Coverage for tasks/apeq_cpft_perinatal.py : 49%

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
3"""
4camcops_server/tasks/apeq_cpft_perinatal.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
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.
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.
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/>.
25===============================================================================
27"""
29from typing import Dict, List, Optional, Tuple, Type
31from cardinal_pythonlib.classes import classproperty
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
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
57# =============================================================================
58# APEQCPFTPerinatal
59# =============================================================================
61class APEQCPFTPerinatal(Task):
62 """
63 Server implementation of the APEQ-CPFT-Perinatal task.
64 """
65 __tablename__ = "apeq_cpft_perinatal"
66 shortname = "APEQ-CPFT-Perinatal"
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)"
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 )
119 REQUIRED_FIELDS = ["q1", "q2", "q3", "q4", "q5", "q6", "ff_rating"]
121 @staticmethod
122 def longname(req: "CamcopsRequest") -> str:
123 _ = req.gettext
124 return _("Assessment Patient Experience Questionnaire for "
125 "CPFT Perinatal Services")
127 def is_complete(self) -> bool:
128 return self.all_fields_not_none(self.REQUIRED_FIELDS)
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}")
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 """
166 def get_main_options(self, req: "CamcopsRequest") -> List[str]:
167 options = []
169 for n in range(0, 2 + 1):
170 options.append(self.wxstring(req, f"main_a{n}"))
172 return options
174 def get_ff_options(self, req: "CamcopsRequest") -> List[str]:
175 options = []
177 for n in range(0, 5 + 1):
178 options.append(self.wxstring(req, f"ff_a{n}"))
180 return options
183# =============================================================================
184# Reports
185# =============================================================================
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
197 COL_FF_WHY = 1
199 def __init__(self, *args, **kwargs):
200 super().__init__(*args, **kwargs)
201 self.task = APEQCPFTPerinatal() # dummy task, never written to DB
203 @classproperty
204 def task_class(self) -> Type["Task"]:
205 return APEQCPFTPerinatal
207 # noinspection PyMethodParameters
208 @classproperty
209 def report_id(cls) -> str:
210 return "apeq_cpft_perinatal"
212 @classmethod
213 def title(cls, req: "CamcopsRequest") -> str:
214 _ = req.gettext
215 return _("APEQ CPFT Perinatal — Question summaries")
217 # noinspection PyMethodParameters
218 @classproperty
219 def superuser_only(cls) -> bool:
220 return False
222 @classmethod
223 def get_specific_http_query_keys(cls) -> List[str]:
224 return [
225 ViewParam.START_DATETIME,
226 ViewParam.END_DATETIME,
227 ]
229 def render_html(self, req: "CamcopsRequest") -> Response:
230 cell_format = "{0:.1f}%"
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 )
249 def get_tsv_pages(self, req: "CamcopsRequest") -> List[TsvPage]:
250 _ = req.gettext
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 )
273 return [main_page, ff_page, ff_why_page, comments_page]
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)
280 return names
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 = {}
289 qnums = range(self.task.FIRST_MAIN_Q, self.task.LAST_MAIN_Q + 1)
291 for qnum in qnums:
292 column_name = f"{self.task.FN_QPREFIX}{qnum}"
294 column_dict[column_name] = self.task.wxstring(req, column_name)
296 return self.get_percentage_summaries(
297 req,
298 column_dict=column_dict,
299 num_answers=3,
300 cell_format=cell_format
301 )
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)
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 )
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 """
330 options = self.task.get_ff_options(req)
332 wheres = [
333 column("ff_rating").isnot(None),
334 column("ff_why").isnot(None)
335 ]
337 self.add_task_report_filters(wheres)
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 )
350 rows = []
352 for result in req.dbsession.execute(query).fetchall():
353 rows.append([options[result[0]], result[1]])
355 return rows
357 def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]:
358 """
359 A list of all the additional comments, as rows.
360 """
362 wheres = [
363 column("comments").isnot(None)
364 ]
366 self.add_task_report_filters(wheres)
368 # noinspection PyUnresolvedReferences
369 query = (
370 select([
371 column("comments"),
372 ])
373 .select_from(self.task.__table__)
374 .where(and_(*wheres))
375 )
377 comment_rows = []
379 for result in req.dbsession.execute(query).fetchall():
380 comment_rows.append(result)
382 return comment_rows
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)]