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

30 

31from cardinal_pythonlib.classes import classproperty 

32 

33from pyramid.renderers import render_to_response 

34from pyramid.response import Response 

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

36from sqlalchemy.sql.schema import Column 

37from sqlalchemy.sql.sqltypes import Integer, UnicodeText 

38 

39from camcops_server.cc_modules.cc_constants import CssClass 

40from camcops_server.cc_modules.cc_html import tr_qa 

41from camcops_server.cc_modules.cc_pyramid import ViewParam 

42from camcops_server.cc_modules.cc_report import ( 

43 DateTimeFilteredReportMixin, 

44 PercentageSummaryReportMixin, 

45 Report, 

46) 

47from camcops_server.cc_modules.cc_request import CamcopsRequest 

48from camcops_server.cc_modules.cc_sqla_coltypes import ( 

49 CamcopsColumn, 

50 ZERO_TO_FIVE_CHECKER, 

51 ZERO_TO_TWO_CHECKER, 

52) 

53from camcops_server.cc_modules.cc_task import Task 

54from camcops_server.cc_modules.cc_tsv import TsvPage 

55 

56 

57# ============================================================================= 

58# APEQCPFTPerinatal 

59# ============================================================================= 

60 

61class APEQCPFTPerinatal(Task): 

62 """ 

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

64 """ 

65 __tablename__ = "apeq_cpft_perinatal" 

66 shortname = "APEQ-CPFT-Perinatal" 

67 

68 FIRST_MAIN_Q = 1 

69 LAST_MAIN_Q = 6 

70 FN_QPREFIX = "q" 

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

72 

73 q1 = CamcopsColumn( 

74 "q1", Integer, 

75 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

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

77 ) 

78 q2 = CamcopsColumn( 

79 "q2", Integer, 

80 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

81 comment="Q2. Felt listened to" + MAIN_EXPLANATION 

82 ) 

83 q3 = CamcopsColumn( 

84 "q3", Integer, 

85 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

86 comment="Q3. Needs were understood" + MAIN_EXPLANATION 

87 ) 

88 q4 = CamcopsColumn( 

89 "q4", Integer, 

90 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

91 comment="Q4. Given info about team" + MAIN_EXPLANATION 

92 ) 

93 q5 = CamcopsColumn( 

94 "q5", Integer, 

95 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

96 comment="Q5. Family considered/included" + MAIN_EXPLANATION 

97 ) 

98 q6 = CamcopsColumn( 

99 "q6", Integer, 

100 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

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

102 ) 

103 ff_rating = CamcopsColumn( 

104 "ff_rating", Integer, 

105 permitted_value_checker=ZERO_TO_FIVE_CHECKER, 

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

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

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

109 ) 

110 ff_why = Column( 

111 "ff_why", UnicodeText, 

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

113 ) 

114 comments = Column( 

115 "comments", UnicodeText, 

116 comment="General comments" 

117 ) 

118 

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

120 

121 @staticmethod 

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

123 _ = req.gettext 

124 return _("Assessment Patient Experience Questionnaire for " 

125 "CPFT Perinatal Services") 

126 

127 def is_complete(self) -> bool: 

128 return self.all_fields_not_none(self.REQUIRED_FIELDS) 

129 

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

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

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

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

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

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

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

137 

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

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

140 xstring_attr_name = f"q{qnum}" 

141 qlines.append(tr_qa( 

142 self.wxstring(req, xstring_attr_name), 

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

144 q_a = "".join(qlines) 

145 return f""" 

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

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

148 {self.get_is_complete_tr(req)} 

149 </table> 

150 </div> 

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

152 <tr> 

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

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

155 </tr> 

156 {q_a} 

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

158 options_ff.get(self.ff_rating))} 

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

160 self.ff_why or "")} 

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

162 self.comments or "")} 

163 </table> 

164 """ 

165 

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

167 options = [] 

168 

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

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

171 

172 return options 

173 

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

175 options = [] 

176 

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

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

179 

180 return options 

181 

182 

183# ============================================================================= 

184# Reports 

185# ============================================================================= 

186 

187class APEQCPFTPerinatalReport(DateTimeFilteredReportMixin, Report, 

188 PercentageSummaryReportMixin): 

189 """ 

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

191 Then a summary of the comments. 

192 """ 

193 COL_Q = 0 

194 COL_TOTAL = 1 

195 COL_RESPONSE_START = 2 

196 

197 COL_FF_WHY = 1 

198 

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

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

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

202 

203 @classproperty 

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

205 return APEQCPFTPerinatal 

206 

207 # noinspection PyMethodParameters 

208 @classproperty 

209 def report_id(cls) -> str: 

210 return "apeq_cpft_perinatal" 

211 

212 @classmethod 

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

214 _ = req.gettext 

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

216 

217 # noinspection PyMethodParameters 

218 @classproperty 

219 def superuser_only(cls) -> bool: 

220 return False 

221 

222 @classmethod 

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

224 return [ 

225 ViewParam.START_DATETIME, 

226 ViewParam.END_DATETIME, 

227 ] 

228 

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

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

231 

232 return render_to_response( 

233 "apeq_cpft_perinatal_report.mako", 

234 dict( 

235 title=self.title(req), 

236 report_id=self.report_id, 

237 start_datetime=self.start_datetime, 

238 end_datetime=self.end_datetime, 

239 main_column_headings=self._get_main_column_headings(req), 

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

241 ff_column_headings=self._get_ff_column_headings(req), 

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

243 ff_why_rows=self._get_ff_why_rows(req), 

244 comments=self._get_comments(req) 

245 ), 

246 request=req 

247 ) 

248 

249 def get_tsv_pages(self, req: "CamcopsRequest") -> List[TsvPage]: 

250 _ = req.gettext 

251 

252 main_page = self.get_tsv_page( 

253 name=_("Main questions"), 

254 column_names=self._get_main_column_headings(req), 

255 rows=self._get_main_rows(req) 

256 ) 

257 ff_page = self.get_tsv_page( 

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

259 column_names=self._get_ff_column_headings(req), 

260 rows=self._get_ff_rows(req) 

261 ) 

262 ff_why_page = self.get_tsv_page( 

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

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

265 rows=self._get_ff_why_rows(req) 

266 ) 

267 comments_page = self.get_tsv_page( 

268 name=_("Comments"), 

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

270 rows=self._get_comment_rows(req) 

271 ) 

272 

273 return [main_page, ff_page, ff_why_page, comments_page] 

274 

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

276 _ = req.gettext 

277 names = [_("Question"), 

278 _("Total responses")] + self.task.get_main_options(req) 

279 

280 return names 

281 

282 def _get_main_rows(self, req: "CamcopsRequest", 

283 cell_format: str = "{}") -> List[List[str]]: 

284 """ 

285 Percentage of people who answered x for each question 

286 """ 

287 column_dict = {} 

288 

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

290 

291 for qnum in qnums: 

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

293 

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

295 

296 return self.get_percentage_summaries( 

297 req, 

298 column_dict=column_dict, 

299 num_answers=3, 

300 cell_format=cell_format 

301 ) 

302 

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

304 _ = req.gettext 

305 return [_("Question"), 

306 _("Total responses")] + self.task.get_ff_options(req) 

307 

308 def _get_ff_rows(self, req: "CamcopsRequest", 

309 cell_format: str = "{}") -> List[List[str]]: 

310 """ 

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

312 """ 

313 return self.get_percentage_summaries( 

314 req, 

315 column_dict={ 

316 "ff_rating": self.task.wxstring( 

317 req, 

318 f"{self.task.FN_QPREFIX}_ff_rating" 

319 ) 

320 }, 

321 num_answers=6, 

322 cell_format=cell_format 

323 ) 

324 

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

326 """ 

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

328 """ 

329 

330 options = self.task.get_ff_options(req) 

331 

332 wheres = [ 

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

334 column("ff_why").isnot(None) 

335 ] 

336 

337 self.add_task_report_filters(wheres) 

338 

339 # noinspection PyUnresolvedReferences 

340 query = ( 

341 select([ 

342 column("ff_rating"), 

343 column("ff_why") 

344 ]) 

345 .select_from(self.task.__table__) 

346 .where(and_(*wheres)) 

347 .order_by("ff_why") 

348 ) 

349 

350 rows = [] 

351 

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

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

354 

355 return rows 

356 

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

358 """ 

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

360 """ 

361 

362 wheres = [ 

363 column("comments").isnot(None) 

364 ] 

365 

366 self.add_task_report_filters(wheres) 

367 

368 # noinspection PyUnresolvedReferences 

369 query = ( 

370 select([ 

371 column("comments"), 

372 ]) 

373 .select_from(self.task.__table__) 

374 .where(and_(*wheres)) 

375 ) 

376 

377 comment_rows = [] 

378 

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

380 comment_rows.append(result) 

381 

382 return comment_rows 

383 

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

385 """ 

386 A list of all the additional comments. 

387 """ 

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