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
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/apeq_cpft_perinatal.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28"""
30from typing import Dict, List, Optional, Tuple, Type
32from cardinal_pythonlib.classes import classproperty
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
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
58# =============================================================================
59# APEQCPFTPerinatal
60# =============================================================================
63class APEQCPFTPerinatal(Task):
64 """
65 Server implementation of the APEQ-CPFT-Perinatal task.
66 """
68 __tablename__ = "apeq_cpft_perinatal"
69 shortname = "APEQ-CPFT-Perinatal"
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)"
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")
127 REQUIRED_FIELDS = ["q1", "q2", "q3", "q4", "q5", "q6", "ff_rating"]
129 @staticmethod
130 def longname(req: "CamcopsRequest") -> str:
131 _ = req.gettext
132 return _(
133 "Assessment Patient Experience Questionnaire for "
134 "CPFT Perinatal Services"
135 )
137 def is_complete(self) -> bool:
138 return self.all_fields_not_none(self.REQUIRED_FIELDS)
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}")
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 """
179 def get_main_options(self, req: "CamcopsRequest") -> List[str]:
180 options = []
182 for n in range(0, 2 + 1):
183 options.append(self.wxstring(req, f"main_a{n}"))
185 return options
187 def get_ff_options(self, req: "CamcopsRequest") -> List[str]:
188 options = []
190 for n in range(0, 5 + 1):
191 options.append(self.wxstring(req, f"ff_a{n}"))
193 return options
196# =============================================================================
197# Reports
198# =============================================================================
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 """
209 COL_Q = 0
210 COL_TOTAL = 1
211 COL_RESPONSE_START = 2
213 COL_FF_WHY = 1
215 def __init__(self, *args, **kwargs):
216 super().__init__(*args, **kwargs)
217 self.task = APEQCPFTPerinatal() # dummy task, never written to DB
219 @classproperty
220 def task_class(self) -> Type["Task"]:
221 return APEQCPFTPerinatal
223 # noinspection PyMethodParameters
224 @classproperty
225 def report_id(cls) -> str:
226 return "apeq_cpft_perinatal"
228 @classmethod
229 def title(cls, req: "CamcopsRequest") -> str:
230 _ = req.gettext
231 return _("APEQ CPFT Perinatal — Question summaries")
233 # noinspection PyMethodParameters
234 @classproperty
235 def superuser_only(cls) -> bool:
236 return False
238 @classmethod
239 def get_specific_http_query_keys(cls) -> List[str]:
240 return [ViewParam.START_DATETIME, ViewParam.END_DATETIME]
242 def render_html(self, req: "CamcopsRequest") -> Response:
243 cell_format = "{0:.1f}%"
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 )
262 def get_spreadsheet_pages(
263 self, req: "CamcopsRequest"
264 ) -> List[SpreadsheetPage]:
265 _ = req.gettext
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 )
288 return [main_page, ff_page, ff_why_page, comments_page]
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)
297 return names
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 = {}
307 qnums = range(self.task.FIRST_MAIN_Q, self.task.LAST_MAIN_Q + 1)
309 for qnum in qnums:
310 column_name = f"{self.task.FN_QPREFIX}{qnum}"
312 column_dict[column_name] = self.task.wxstring(req, column_name)
314 return self.get_percentage_summaries(
315 req,
316 column_dict=column_dict,
317 num_answers=3,
318 cell_format=cell_format,
319 )
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)
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 )
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 """
350 options = self.task.get_ff_options(req)
352 wheres = [
353 column("ff_rating").isnot(None),
354 column("ff_why").isnot(None),
355 ]
357 self.add_task_report_filters(wheres)
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 )
367 rows = []
369 for result in req.dbsession.execute(query).fetchall():
370 rows.append([options[result[0]], result[1]])
372 return rows
374 def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]:
375 """
376 A list of all the additional comments, as rows.
377 """
379 wheres = [column("comments").isnot(None)]
381 self.add_task_report_filters(wheres)
383 # noinspection PyUnresolvedReferences
384 query = (
385 select([column("comments")])
386 .select_from(self.task.__table__)
387 .where(and_(*wheres))
388 )
390 comment_rows = []
392 for result in req.dbsession.execute(query).fetchall():
393 comment_rows.append(result)
395 return comment_rows
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)]