Coverage for tasks/perinatalpoem.py : 56%

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/perinatalpoem.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"""
29import re
30from typing import Dict, List, Tuple, Type
32from 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
41from camcops_server.cc_modules.cc_html import (
42 get_yes_no_none,
43 subheading_spanning_two_columns,
44 tr_qa,
45)
46from camcops_server.cc_modules.cc_report import (
47 DateTimeFilteredReportMixin,
48 PercentageSummaryReportMixin,
49 Report,
50)
51from camcops_server.cc_modules.cc_request import CamcopsRequest
52from camcops_server.cc_modules.cc_sqla_coltypes import (
53 CamcopsColumn,
54 ZERO_TO_ONE_CHECKER,
55 ONE_TO_TWO_CHECKER,
56 ONE_TO_FIVE_CHECKER,
57 ONE_TO_FOUR_CHECKER,
58)
59from camcops_server.cc_modules.cc_task import (
60 get_from_dict,
61 Task,
62)
63from camcops_server.cc_modules.cc_text import SS
64from camcops_server.cc_modules.cc_tsv import TsvPage
67# =============================================================================
68# Perinatal-POEM
69# =============================================================================
71class PerinatalPoem(Task):
72 """
73 Server implementation of the Perinatal-POEM task.
74 """
75 __tablename__ = "perinatal_poem"
76 shortname = "Perinatal-POEM"
77 provides_trackers = False
79 # Field names
80 FN_QA_RESPONDENT = "qa"
81 FN_QB_SERVICE_TYPE = "qb"
82 FN_Q1A_MH_FIRST_CONTACT = "q1a"
83 FN_Q1B_MH_DISCHARGE = "q1b"
84 FN_Q2A_STAFF_DID_NOT_COMMUNICATE = "q2a"
85 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT = "q2b"
86 FN_Q2C_HELP_NOT_QUICK_ENOUGH = "q2c"
87 FN_Q2D_STAFF_LISTENED = "q2d"
88 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME = "q2e"
89 FN_Q2F_SERVICE_PROVIDED_INFO = "q2f"
90 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME = "q2g"
91 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND = "q2h"
92 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY = "q2i"
93 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE = "q2j"
94 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY = "q2k"
95 FN_Q2L_I_WOULD_RECOMMEND_SERVICE = "q2l"
96 FN_Q3A_UNIT_CLEAN = "q3a"
97 FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER = "q3b"
98 FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES = "q3c"
99 FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY = "q3d"
100 FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT = "q3e"
101 FN_Q3F_FOOD_NOT_ACCEPTABLE = "q3f"
102 FN_GENERAL_COMMENTS = "general_comments"
103 FN_FUTURE_PARTICIPATION = "future_participation"
104 FN_CONTACT_DETAILS = "contact_details"
106 # Response values
107 VAL_QA_PATIENT = 1
108 VAL_QA_PARTNER_OTHER = 2
110 VAL_QB_INPATIENT = 1 # inpatient = MBU = mother and baby unit
111 VAL_QB_COMMUNITY = 2
113 VAL_Q1_VERY_WELL = 1
114 VAL_Q1_WELL = 2
115 VAL_Q1_UNWELL = 3
116 VAL_Q1_VERY_UNWELL = 4
117 VAL_Q1_EXTREMELY_UNWELL = 5
118 _MH_KEY = (
119 f"({VAL_Q1_VERY_WELL} very well, {VAL_Q1_WELL} well, "
120 f"{VAL_Q1_UNWELL} unwell, {VAL_Q1_VERY_UNWELL} very unwell, "
121 f"{VAL_Q1_EXTREMELY_UNWELL} extremely unwell)"
122 )
124 VAL_STRONGLY_AGREE = 1
125 VAL_AGREE = 2
126 VAL_DISAGREE = 3
127 VAL_STRONGLY_DISAGREE = 4
128 _AGREE_KEY = (
129 f"({VAL_STRONGLY_AGREE} strongly agree, {VAL_AGREE} agree, "
130 f"{VAL_DISAGREE} disagree, {VAL_STRONGLY_DISAGREE} strongly disagree)"
131 )
133 _INPATIENT_ONLY = "[Inpatient services only]"
135 YES_INT = 1
136 NO_INT = 0
138 # -------------------------------------------------------------------------
139 # Fields
140 # -------------------------------------------------------------------------
141 qa = CamcopsColumn(
142 FN_QA_RESPONDENT, Integer,
143 permitted_value_checker=ONE_TO_TWO_CHECKER,
144 comment=(
145 f"Question A: Is the respondent the patient ({VAL_QA_PATIENT}) "
146 f"or other ({VAL_QA_PARTNER_OTHER})?"
147 )
148 )
149 qb = CamcopsColumn(
150 FN_QB_SERVICE_TYPE, Integer,
151 permitted_value_checker=ONE_TO_TWO_CHECKER,
152 comment=(
153 f"Question B: Was the service type inpatient [mother-and-baby "
154 f"unit, MBU] ({VAL_QB_INPATIENT}) or "
155 f"community ({VAL_QB_COMMUNITY})?"
156 )
157 )
159 q1a = CamcopsColumn(
160 FN_Q1A_MH_FIRST_CONTACT, Integer,
161 permitted_value_checker=ONE_TO_FIVE_CHECKER,
162 comment=f"Q1A: mental health at first contact {_MH_KEY}"
163 )
164 q1b = CamcopsColumn(
165 FN_Q1B_MH_DISCHARGE, Integer,
166 permitted_value_checker=ONE_TO_FIVE_CHECKER,
167 comment=f"Q1B: mental health at discharge {_MH_KEY}"
168 )
170 q2a = CamcopsColumn(
171 FN_Q2A_STAFF_DID_NOT_COMMUNICATE, Integer,
172 permitted_value_checker=ONE_TO_FOUR_CHECKER,
173 comment=f"Q2a: staff didn't communicate with others {_AGREE_KEY}"
174 )
175 q2b = CamcopsColumn(
176 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT, Integer,
177 permitted_value_checker=ONE_TO_FOUR_CHECKER,
178 comment=f"Q2b: Staff gave right amount of support {_AGREE_KEY}"
179 )
180 q2c = CamcopsColumn(
181 FN_Q2C_HELP_NOT_QUICK_ENOUGH, Integer,
182 permitted_value_checker=ONE_TO_FOUR_CHECKER,
183 comment=f"Q2c: Help not quick enough after referral {_AGREE_KEY}"
184 )
185 q2d = CamcopsColumn(
186 FN_Q2D_STAFF_LISTENED, Integer,
187 permitted_value_checker=ONE_TO_FOUR_CHECKER,
188 comment=f"Q2d: Staff listened/understood {_AGREE_KEY}"
189 )
191 q2e = CamcopsColumn(
192 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME, Integer,
193 permitted_value_checker=ONE_TO_FOUR_CHECKER,
194 comment=f"Q2e: Staff didn't involve pt enough {_AGREE_KEY}"
195 )
196 q2f = CamcopsColumn(
197 FN_Q2F_SERVICE_PROVIDED_INFO, Integer,
198 permitted_value_checker=ONE_TO_FOUR_CHECKER,
199 comment=f"Q2f: Service provided information {_AGREE_KEY}"
200 )
201 q2g = CamcopsColumn(
202 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME, Integer,
203 permitted_value_checker=ONE_TO_FOUR_CHECKER,
204 comment=f"Q2g: Staff not very sensitive to pt {_AGREE_KEY}"
205 )
206 q2h = CamcopsColumn(
207 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND, Integer,
208 permitted_value_checker=ONE_TO_FOUR_CHECKER,
209 comment=f"Q2h: Staff helped understanding of illness {_AGREE_KEY}"
210 )
212 q2i = CamcopsColumn(
213 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY, Integer,
214 permitted_value_checker=ONE_TO_FOUR_CHECKER,
215 comment=f"Q2i: Staff not very sensitive to baby {_AGREE_KEY}"
216 )
217 q2j = CamcopsColumn(
218 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE, Integer,
219 permitted_value_checker=ONE_TO_FOUR_CHECKER,
220 comment=f"Q2j: Staff helped confidence re baby {_AGREE_KEY}"
221 )
222 q2k = CamcopsColumn(
223 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY, Integer,
224 permitted_value_checker=ONE_TO_FOUR_CHECKER,
225 comment=f"Q2k: Service involved others helpfully {_AGREE_KEY}"
226 )
227 q2l = CamcopsColumn(
228 FN_Q2L_I_WOULD_RECOMMEND_SERVICE, Integer,
229 permitted_value_checker=ONE_TO_FOUR_CHECKER,
230 comment=f"Q2l: Would recommend service {_AGREE_KEY}"
231 )
233 q3a = CamcopsColumn(
234 FN_Q3A_UNIT_CLEAN, Integer,
235 permitted_value_checker=ONE_TO_FOUR_CHECKER,
236 comment=f"Q3a: MBU clean {_AGREE_KEY} {_INPATIENT_ONLY}"
237 )
238 q3b = CamcopsColumn(
239 FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER, Integer,
240 permitted_value_checker=ONE_TO_FOUR_CHECKER,
241 comment=f"Q3b: MBU not a good place to recover "
242 f"{_AGREE_KEY} {_INPATIENT_ONLY}"
243 )
244 q3c = CamcopsColumn(
245 FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES, Integer,
246 permitted_value_checker=ONE_TO_FOUR_CHECKER,
247 comment=f"Q3c: MBU did not provide helpful activities "
248 f"{_AGREE_KEY} {_INPATIENT_ONLY}"
249 )
250 q3d = CamcopsColumn(
251 FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY, Integer,
252 permitted_value_checker=ONE_TO_FOUR_CHECKER,
253 comment=f"Q3d: MBU a good place for baby to be with pt "
254 f"{_AGREE_KEY} {_INPATIENT_ONLY}"
255 )
256 q3e = CamcopsColumn(
257 FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT, Integer,
258 permitted_value_checker=ONE_TO_FOUR_CHECKER,
259 comment=f"Q3e: MBU supported contact with family/friends "
260 f"{_AGREE_KEY} {_INPATIENT_ONLY}"
261 )
262 q3f = CamcopsColumn(
263 FN_Q3F_FOOD_NOT_ACCEPTABLE, Integer,
264 permitted_value_checker=ONE_TO_FOUR_CHECKER,
265 comment=f"Q3f: Food not acceptable {_AGREE_KEY} {_INPATIENT_ONLY}"
266 )
268 general_comments = Column(
269 FN_GENERAL_COMMENTS, UnicodeText,
270 comment="General comments"
271 )
272 future_participation = CamcopsColumn(
273 FN_FUTURE_PARTICIPATION, Integer,
274 permitted_value_checker=ZERO_TO_ONE_CHECKER,
275 comment=f"Willing to participate in future studies "
276 f"({YES_INT} yes, {NO_INT} no)"
277 )
278 contact_details = Column(
279 FN_CONTACT_DETAILS, UnicodeText,
280 comment="Contact details"
281 )
283 # -------------------------------------------------------------------------
284 # Fieldname collections
285 # -------------------------------------------------------------------------
286 REQUIRED_ALWAYS = [
287 FN_QA_RESPONDENT,
288 FN_QB_SERVICE_TYPE,
289 FN_Q1A_MH_FIRST_CONTACT,
290 FN_Q1B_MH_DISCHARGE,
291 FN_Q2A_STAFF_DID_NOT_COMMUNICATE,
292 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT,
293 FN_Q2C_HELP_NOT_QUICK_ENOUGH,
294 FN_Q2D_STAFF_LISTENED,
295 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME,
296 FN_Q2F_SERVICE_PROVIDED_INFO,
297 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME,
298 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND,
299 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY,
300 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE,
301 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY,
302 FN_Q2L_I_WOULD_RECOMMEND_SERVICE,
303 # not FN_GENERAL_COMMENTS,
304 FN_FUTURE_PARTICIPATION,
305 # not FN_CONTACT_DETAILS,
306 ]
307 REQUIRED_INPATIENT = [
308 FN_Q3A_UNIT_CLEAN,
309 FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER,
310 FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES,
311 FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY,
312 FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT,
313 FN_Q3F_FOOD_NOT_ACCEPTABLE,
314 ]
315 Q1_FIELDS = [
316 FN_Q1A_MH_FIRST_CONTACT,
317 FN_Q1B_MH_DISCHARGE,
318 ]
319 Q2_FIELDS = [
320 FN_Q2A_STAFF_DID_NOT_COMMUNICATE,
321 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT,
322 FN_Q2C_HELP_NOT_QUICK_ENOUGH,
323 FN_Q2D_STAFF_LISTENED,
324 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME,
325 FN_Q2F_SERVICE_PROVIDED_INFO,
326 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME,
327 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND,
328 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY,
329 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE,
330 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY,
331 FN_Q2L_I_WOULD_RECOMMEND_SERVICE,
332 ]
333 Q3_FIELDS = REQUIRED_INPATIENT
335 @staticmethod
336 def longname(req: "CamcopsRequest") -> str:
337 _ = req.gettext
338 return _("Perinatal Patient-rated Outcome and Experience Measure")
340 def was_inpatient(self) -> bool:
341 return self.qb == self.VAL_QB_INPATIENT
343 def respondent_not_patient(self) -> bool:
344 return self.qa == self.VAL_QA_PARTNER_OTHER
346 def offering_participation(self) -> bool:
347 return self.future_participation == self.YES_INT
349 def is_complete(self) -> bool:
350 if self.any_fields_none(self.REQUIRED_ALWAYS):
351 return False
352 if (self.was_inpatient() and
353 self.any_fields_none(self.REQUIRED_INPATIENT)):
354 return False
355 if not self.field_contents_valid():
356 return False
357 return True
359 def get_qa_options(self, req: CamcopsRequest) -> List[str]:
360 options = [self.wxstring(req, f"qa_a{o}") for o in range(
361 self.VAL_QA_PATIENT,
362 self.VAL_QA_PARTNER_OTHER + 1)]
364 return options
366 def get_qb_options(self, req: CamcopsRequest) -> List[str]:
367 options = [self.wxstring(req, f"qb_a{o}") for o in range(
368 self.VAL_QB_INPATIENT,
369 self.VAL_QB_COMMUNITY + 1)]
371 return options
373 def get_q1_options(self, req: CamcopsRequest) -> List[str]:
374 options = [self.wxstring(req, f"q1_a{o}") for o in range(
375 self.VAL_Q1_VERY_WELL,
376 self.VAL_Q1_EXTREMELY_UNWELL + 1)]
378 return options
380 def get_agree_options(self, req: CamcopsRequest) -> List[str]:
381 options = [self.wxstring(req, f"agreement_a{o}") for o in range(
382 self.VAL_STRONGLY_AGREE,
383 self.VAL_STRONGLY_DISAGREE + 1)]
385 return options
387 @staticmethod
388 def get_yn_options(req: CamcopsRequest) -> List[str]:
389 return [req.sstring(SS.NO), req.sstring(SS.YES)]
391 def get_task_html(self, req: CamcopsRequest) -> str:
392 def loadvalues(_dict: Dict[int, str], _first: int, _last: int,
393 _xstringprefix: str) -> None:
394 for val in range(_first, _last + 1):
395 _dict[val] = (
396 f"{val} — {self.wxstring(req, f'{_xstringprefix}{val}')}"
397 )
399 respondent_dict = {} # type: Dict[int, str]
400 loadvalues(respondent_dict, self.VAL_QA_PATIENT,
401 self.VAL_QA_PARTNER_OTHER, "qa_a")
402 service_dict = {} # type: Dict[int, str]
403 loadvalues(service_dict, self.VAL_QB_INPATIENT,
404 self.VAL_QB_COMMUNITY, "qb_a")
405 mh_dict = {} # type: Dict[int, str]
406 loadvalues(mh_dict, self.VAL_Q1_VERY_WELL,
407 self.VAL_Q1_EXTREMELY_UNWELL, "q1_a")
408 agree_dict = {} # type: Dict[int, str]
409 loadvalues(agree_dict, self.VAL_STRONGLY_AGREE,
410 self.VAL_STRONGLY_DISAGREE, "agreement_a")
412 q_a_list = [] # type: List[str]
414 def addqa(_fieldname: str, _valuedict: Dict[int, str]) -> None:
415 xstringname = _fieldname + "_q"
416 q_a_list.append(
417 tr_qa(self.xstring(req, xstringname), # not wxstring
418 get_from_dict(_valuedict, getattr(self, _fieldname)))
419 )
421 def subheading(_xstringname: str) -> None:
422 q_a_list.append(subheading_spanning_two_columns(
423 self.wxstring(req, _xstringname)))
425 # Preamble
426 addqa(self.FN_QA_RESPONDENT, respondent_dict)
427 addqa(self.FN_QB_SERVICE_TYPE, service_dict)
428 # The bulk
429 subheading("q1_stem")
430 for fieldname in self.Q1_FIELDS:
431 addqa(fieldname, mh_dict)
432 subheading("q2_stem")
433 for fieldname in self.Q2_FIELDS:
434 addqa(fieldname, agree_dict)
435 if self.was_inpatient():
436 subheading("q3_stem")
437 for fieldname in self.Q3_FIELDS:
438 addqa(fieldname, agree_dict)
439 # General
440 q_a_list.append(subheading_spanning_two_columns(
441 req.sstring(SS.GENERAL)))
442 q_a_list.append(tr_qa(
443 self.wxstring(req, "general_comments_q"),
444 self.general_comments
445 ))
446 q_a_list.append(tr_qa(
447 self.wxstring(req, "participation_q"),
448 get_yes_no_none(req, self.future_participation)
449 ))
450 if self.offering_participation():
451 q_a_list.append(tr_qa(
452 self.wxstring(req, "contact_details_q"),
453 self.contact_details
454 ))
456 q_a = "\n".join(q_a_list)
457 return f"""
458 <div class="{CssClass.SUMMARY}">
459 <table class="{CssClass.SUMMARY}">
460 {self.get_is_complete_tr(req)}
461 </table>
462 </div>
463 <table class="{CssClass.TASKDETAIL}">
464 <tr>
465 <th width="60%">Question</th>
466 <th width="40%">Answer</th>
467 </tr>
468 {q_a}
469 </table>
470 <div class="{CssClass.FOOTNOTES}">
471 </div>
472 """
474 # No SNOMED codes for Perinatal-POEM.
477# =============================================================================
478# Reports
479# =============================================================================
481class PerinatalPoemReportTableConfig(object):
482 def __init__(self,
483 heading: str,
484 column_headings: List[str],
485 fieldnames: List[str],
486 min_answer: int = 0,
487 xstring_format: str = "{}_q") -> None:
488 self.heading = heading
489 self.column_headings = column_headings
490 self.fieldnames = fieldnames
491 self.min_answer = min_answer
492 self.xstring_format = xstring_format
495class PerinatalPoemReportTable(object):
496 def __init__(self, req: "CamcopsRequest",
497 heading: str,
498 column_headings: List[str],
499 rows: List[List[str]]) -> None:
500 _ = req.gettext
501 self.heading = heading
503 common_headings = [_("Question"), _("Total responses")]
504 self.column_headings = common_headings + column_headings
505 self.rows = rows
508class PerinatalPoemReport(DateTimeFilteredReportMixin, Report,
509 PercentageSummaryReportMixin):
510 """
511 Provides a summary of each question, x% of people said each response etc.
512 Then a summary of the comments.
513 """
514 HTML_TAG_RE = re.compile(r'<[^>]+>')
516 def __init__(self, *args, **kwargs):
517 super().__init__(*args, **kwargs)
518 self.task = PerinatalPoem() # dummy task, never written to DB
520 @classproperty
521 def task_class(self) -> Type["Task"]:
522 return PerinatalPoem
524 # noinspection PyMethodParameters
525 @classproperty
526 def report_id(cls) -> str:
527 return "perinatal_poem"
529 @classmethod
530 def title(cls, req: "CamcopsRequest") -> str:
531 _ = req.gettext
532 return _("Perinatal-POEM — Question summaries")
534 # noinspection PyMethodParameters
535 @classproperty
536 def superuser_only(cls) -> bool:
537 return False
539 def render_html(self, req: "CamcopsRequest") -> Response:
540 return render_to_response(
541 "perinatal_poem_report.mako",
542 dict(
543 title=self.title(req),
544 report_id=self.report_id,
545 start_datetime=self.start_datetime,
546 end_datetime=self.end_datetime,
547 tables=self._get_html_tables(req),
548 comments=self._get_comments(req)
549 ),
550 request=req
551 )
553 def get_tsv_pages(self, req: "CamcopsRequest") -> List[TsvPage]:
554 _ = req.gettext
556 pages = []
558 for table in self._get_tsv_tables(req):
559 pages.append(
560 self.get_tsv_page(
561 name=table.heading,
562 column_names=table.column_headings,
563 rows=table.rows
564 )
565 )
567 pages.append(
568 self.get_tsv_page(
569 name=_("Comments"),
570 column_names=[_("Comment")],
571 rows=self._get_comment_rows(req)
572 )
573 )
575 return pages
577 def _get_html_tables(
578 self, req: "CamcopsRequest") -> List["PerinatalPoemReportTable"]:
580 return [
581 self._get_html_table(req, config)
582 for config in self._get_table_configs(req)
583 ]
585 def _get_tsv_tables(
586 self, req: "CamcopsRequest") -> List["PerinatalPoemReportTable"]:
588 return [
589 self._get_tsv_table(req, config)
590 for config in self._get_table_configs(req)
591 ]
593 def _get_table_configs(
594 self,
595 req: "CamcopsRequest") -> List["PerinatalPoemReportTableConfig"]:
596 return [
597 PerinatalPoemReportTableConfig(
598 heading=self.task.xstring(req, "qa_q"),
599 column_headings=self.task.get_qa_options(req),
600 fieldnames=["qa"],
601 min_answer=1
602 ),
603 PerinatalPoemReportTableConfig(
604 heading=self.task.xstring(req, "qb_q"),
605 column_headings=self.task.get_qb_options(req),
606 fieldnames=["qb"],
607 min_answer=1
608 ),
609 PerinatalPoemReportTableConfig(
610 heading=self.task.xstring(req, "q1_stem"),
611 column_headings=self.task.get_q1_options(req),
612 fieldnames=PerinatalPoem.Q1_FIELDS,
613 min_answer=1
614 ),
615 PerinatalPoemReportTableConfig(
616 heading=self.task.xstring(req, "q2_stem"),
617 column_headings=self.task.get_agree_options(req),
618 fieldnames=PerinatalPoem.Q2_FIELDS,
619 min_answer=1
620 ),
621 PerinatalPoemReportTableConfig(
622 heading=self.task.xstring(req, "q3_stem"),
623 column_headings=self.task.get_agree_options(req),
624 fieldnames=PerinatalPoem.Q3_FIELDS,
625 min_answer=1
626 ),
627 PerinatalPoemReportTableConfig(
628 heading=self.task.xstring(req, "participation_q"),
629 column_headings=self.task.get_yn_options(req),
630 fieldnames=["future_participation"],
631 xstring_format="participation_q"
632 ),
633 ]
635 def _get_html_table(
636 self, req: "CamcopsRequest",
637 config: PerinatalPoemReportTableConfig
638 ) -> PerinatalPoemReportTable:
639 column_dict = {}
641 for fieldname in config.fieldnames:
642 column_dict[fieldname] = self.task.xstring(
643 req, config.xstring_format.format(fieldname)
644 )
646 rows = self.get_percentage_summaries(
647 req,
648 column_dict=column_dict,
649 num_answers=len(config.column_headings),
650 cell_format="{0:.1f}%",
651 min_answer=config.min_answer
652 )
654 return PerinatalPoemReportTable(
655 req,
656 heading=config.heading,
657 column_headings=config.column_headings,
658 rows=rows
659 )
661 def _get_tsv_table(
662 self, req: "CamcopsRequest",
663 config: PerinatalPoemReportTableConfig
664 ) -> PerinatalPoemReportTable:
665 column_dict = {}
667 for fieldname in config.fieldnames:
668 column_dict[fieldname] = self._strip_tags(
669 self.task.xstring(
670 req, config.xstring_format.format(fieldname)
671 )
672 )
674 rows = self.get_percentage_summaries(
675 req,
676 column_dict=column_dict,
677 num_answers=len(config.column_headings),
678 min_answer=config.min_answer
679 )
681 return PerinatalPoemReportTable(
682 req,
683 heading=config.heading,
684 column_headings=config.column_headings,
685 rows=rows
686 )
688 def _strip_tags(self, text: str) -> str:
689 return self.HTML_TAG_RE.sub('', text)
691 def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]:
692 """
693 A list of all the additional comments
694 """
696 wheres = [
697 column("general_comments").isnot(None)
698 ]
700 self.add_task_report_filters(wheres)
702 # noinspection PyUnresolvedReferences
703 query = (
704 select([
705 column("general_comments"),
706 ])
707 .select_from(self.task.__table__)
708 .where(and_(*wheres))
709 )
711 comment_rows = []
713 for result in req.dbsession.execute(query).fetchall():
714 comment_rows.append(result)
716 return comment_rows
718 def _get_comments(self, req: "CamcopsRequest") -> List[str]:
719 """
720 A list of all the additional comments.
721 """
722 return [x[0] for x in self._get_comment_rows(req)]