Coverage for tasks/apeq_cpft_perinatal.py: 48%

1"""

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

28 from typing import Any, Dict, List, Optional, Tuple, Type

29

30 from cardinal_pythonlib.classes import classproperty

31

32 from pyramid.renderers import render_to_response

33 from pyramid.response import Response

34 from sqlalchemy.orm import Mapped, mapped_column

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

36 from sqlalchemy.sql.sqltypes import UnicodeText

37

38 from camcops_server.cc_modules.cc_constants import CssClass

39 from camcops_server.cc_modules.cc_html import tr_qa

40 from camcops_server.cc_modules.cc_pyramid import ViewParam

41 from camcops_server.cc_modules.cc_report import (

42 DateTimeFilteredReportMixin,

43 PercentageSummaryReportMixin,

44 Report,

45)

46 from camcops_server.cc_modules.cc_request import CamcopsRequest

47 from camcops_server.cc_modules.cc_sqla_coltypes import (

48 mapped_camcops_column,

49 ZERO_TO_FIVE_CHECKER,

50 ZERO_TO_TWO_CHECKER,

51)

52 from camcops_server.cc_modules.cc_task import Task

53 from camcops_server.cc_modules.cc_spreadsheet import SpreadsheetPage

54

55

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

57# APEQCPFTPerinatal

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

59

60

61 class APEQCPFTPerinatal(Task):

62 """

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

64 """

65

66 __tablename__ = "apeq_cpft_perinatal"

67 shortname = "APEQ-CPFT-Perinatal"

68

69 FIRST_MAIN_Q = 1

70 LAST_MAIN_Q = 6

71 FN_QPREFIX = "q"

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

73

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

75 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

77 )

78 q2: Mapped[Optional[int]] = mapped_camcops_column(

79 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

81 )

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

83 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

85 )

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

87 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

89 )

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

91 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

93 )

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

95 permitted_value_checker=ZERO_TO_TWO_CHECKER,

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

97 )

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

99 permitted_value_checker=ZERO_TO_FIVE_CHECKER,

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

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

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

103 )

104 ff_why: Mapped[Optional[str]] = mapped_column(

105 UnicodeText,

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

107 )

108 comments: Mapped[Optional[str]] = mapped_column(

109 UnicodeText, comment="General comments"

110 )

111

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

113

114 @staticmethod

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

116 _ = req.gettext

117 return _(

118 "Assessment Patient Experience Questionnaire for "

119 "CPFT Perinatal Services"

120 )

121

122 def is_complete(self) -> bool:

123 return self.all_fields_not_none(self.REQUIRED_FIELDS)

124

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

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

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

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

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

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

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

132

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

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

135 xstring_attr_name = f"q{qnum}"

136 qlines.append(

137 tr_qa(

138 self.wxstring(req, xstring_attr_name),

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

140 )

141 )

142 q_a = "".join(qlines)

143 return f"""

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

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

146 {self.get_is_complete_tr(req)}

147 </table>

148 </div>

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

150 <tr>

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

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

153 </tr>

154 {q_a}

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

156 options_ff.get(self.ff_rating))}

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

158 self.ff_why or "")}

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

160 self.comments or "")}

161 </table>

162 """

163

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

165 options = []

166

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

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

169

170 return options

171

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

173 options = []

174

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

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

177

178 return options

179

180

181# =============================================================================

182# Reports

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

184

185

186 class APEQCPFTPerinatalReport(

187 DateTimeFilteredReportMixin, Report, PercentageSummaryReportMixin

188):

189 """

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

191 Then a summary of the comments.

192 """

193

194 COL_Q = 0

195 COL_TOTAL = 1

196 COL_RESPONSE_START = 2

197

198 COL_FF_WHY = 1

199

200 def __init__(self, *args: Any, **kwargs: Any) -> None:

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

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

203

204 @classproperty

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

206 return APEQCPFTPerinatal

207

208 # noinspection PyMethodParameters

209 @classproperty

210 def report_id(cls) -> str:

211 return "apeq_cpft_perinatal"

212

213 @classmethod

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

215 _ = req.gettext

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

217

218 # noinspection PyMethodParameters

219 @classproperty

220 def superuser_only(cls) -> bool:

221 return False

222

223 @classmethod

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

225 return [ViewParam.START_DATETIME, ViewParam.END_DATETIME]

226

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

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

229

230 return render_to_response(

231 "apeq_cpft_perinatal_report.mako",

232 dict(

233 title=self.title(req),

234 report_id=self.report_id,

235 start_datetime=self.start_datetime,

236 end_datetime=self.end_datetime,

237 main_column_headings=self._get_main_column_headings(req),

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

239 ff_column_headings=self._get_ff_column_headings(req),

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

241 ff_why_rows=self._get_ff_why_rows(req),

242 comments=self._get_comments(req),

243 ),

244 request=req,

245 )

246

247 def get_spreadsheet_pages(

248 self, req: "CamcopsRequest"

249 ) -> List[SpreadsheetPage]:

250 _ = req.gettext

251

252 main_page = self.get_spreadsheet_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_spreadsheet_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_spreadsheet_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_spreadsheet_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 = [

278 _("Question"),

279 _("Total responses"),

280 ] + self.task.get_main_options(req)

281

282 return names

283

284 def _get_main_rows(

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

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

287 """

288 Percentage of people who answered x for each question

289 """

290 column_dict = {}

291

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

293

294 for qnum in qnums:

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

296

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

298

299 return self.get_percentage_summaries(

300 req,

301 column_dict=column_dict,

302 num_answers=3,

303 cell_format=cell_format,

304 )

305

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

307 _ = req.gettext

308 return [

309 _("Question"),

310 _("Total responses"),

311 ] + self.task.get_ff_options(req)

312

313 def _get_ff_rows(

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

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

316 """

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

318 """

319 return self.get_percentage_summaries(

320 req,

321 column_dict={

322 "ff_rating": self.task.wxstring(

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

324 )

325 },

326 num_answers=6,

327 cell_format=cell_format,

328 )

329

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

331 """

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

333 """

334

335 options = self.task.get_ff_options(req)

336

337 wheres = [

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

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

340 ]

341

342 self.add_task_report_filters(wheres) # type: ignore[arg-type]

343

344 # noinspection PyUnresolvedReferences

345 query = (

346 select(column("ff_rating"), column("ff_why")) # type: ignore[var-annotated] # noqa: E501

347 .select_from(self.task.__table__)

348 .where(and_(*wheres))

349 .order_by("ff_why")

350 )

351

352 rows = []

353

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

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

356

357 return rows

358

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

360 """

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

362 """

363

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

365

366 self.add_task_report_filters(wheres) # type: ignore[arg-type]

367

368 # noinspection PyUnresolvedReferences

369 query = (

370 select(column("comments")) # type: ignore[var-annotated]

371 .select_from(self.task.__table__)

372 .where(and_(*wheres))

373 )

374

375 comment_rows = []

376

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

378 comment_rows.append(result)

379

380 return comment_rows

381

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

383 """

384 A list of all the additional comments.

385 """

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