Coverage for tasks/perinatalpoem.py: 56%
235 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/perinatalpoem.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28import re
29from typing import Any, Dict, List, Optional, Tuple, Type
31from cardinal_pythonlib.classes import classproperty
32from pyramid.renderers import render_to_response
33from pyramid.response import Response
34from sqlalchemy import Select
35from sqlalchemy.orm import Mapped, mapped_column
36from sqlalchemy.sql.expression import and_, column, select
37from sqlalchemy.sql.sqltypes import 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 mapped_camcops_column,
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 get_from_dict, Task
60from camcops_server.cc_modules.cc_text import SS
61from camcops_server.cc_modules.cc_spreadsheet import SpreadsheetPage
64# =============================================================================
65# Perinatal-POEM
66# =============================================================================
69class PerinatalPoem(Task):
70 """
71 Server implementation of the Perinatal-POEM task.
72 """
74 __tablename__ = "perinatal_poem"
75 shortname = "Perinatal-POEM"
76 provides_trackers = False
78 # Field names
79 FN_QA_RESPONDENT = "qa"
80 FN_QB_SERVICE_TYPE = "qb"
81 FN_Q1A_MH_FIRST_CONTACT = "q1a"
82 FN_Q1B_MH_DISCHARGE = "q1b"
83 FN_Q2A_STAFF_DID_NOT_COMMUNICATE = "q2a"
84 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT = "q2b"
85 FN_Q2C_HELP_NOT_QUICK_ENOUGH = "q2c"
86 FN_Q2D_STAFF_LISTENED = "q2d"
87 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME = "q2e"
88 FN_Q2F_SERVICE_PROVIDED_INFO = "q2f"
89 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME = "q2g"
90 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND = "q2h"
91 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY = "q2i"
92 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE = "q2j"
93 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY = "q2k"
94 FN_Q2L_I_WOULD_RECOMMEND_SERVICE = "q2l"
95 FN_Q3A_UNIT_CLEAN = "q3a"
96 FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER = "q3b"
97 FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES = "q3c"
98 FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY = "q3d"
99 FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT = "q3e"
100 FN_Q3F_FOOD_NOT_ACCEPTABLE = "q3f"
101 FN_GENERAL_COMMENTS = "general_comments"
102 FN_FUTURE_PARTICIPATION = "future_participation"
103 FN_CONTACT_DETAILS = "contact_details"
105 # Response values
106 VAL_QA_PATIENT = 1
107 VAL_QA_PARTNER_OTHER = 2
109 VAL_QB_INPATIENT = 1 # inpatient = MBU = mother and baby unit
110 VAL_QB_COMMUNITY = 2
112 VAL_Q1_VERY_WELL = 1
113 VAL_Q1_WELL = 2
114 VAL_Q1_UNWELL = 3
115 VAL_Q1_VERY_UNWELL = 4
116 VAL_Q1_EXTREMELY_UNWELL = 5
117 _MH_KEY = (
118 f"({VAL_Q1_VERY_WELL} very well, {VAL_Q1_WELL} well, "
119 f"{VAL_Q1_UNWELL} unwell, {VAL_Q1_VERY_UNWELL} very unwell, "
120 f"{VAL_Q1_EXTREMELY_UNWELL} extremely unwell)"
121 )
123 VAL_STRONGLY_AGREE = 1
124 VAL_AGREE = 2
125 VAL_DISAGREE = 3
126 VAL_STRONGLY_DISAGREE = 4
127 _AGREE_KEY = (
128 f"({VAL_STRONGLY_AGREE} strongly agree, {VAL_AGREE} agree, "
129 f"{VAL_DISAGREE} disagree, {VAL_STRONGLY_DISAGREE} strongly disagree)"
130 )
132 _INPATIENT_ONLY = "[Inpatient services only]"
134 YES_INT = 1
135 NO_INT = 0
137 # -------------------------------------------------------------------------
138 # Fields
139 # -------------------------------------------------------------------------
140 qa: Mapped[Optional[int]] = mapped_camcops_column(
141 permitted_value_checker=ONE_TO_TWO_CHECKER,
142 comment=(
143 f"Question A: Is the respondent the patient ({VAL_QA_PATIENT}) "
144 f"or other ({VAL_QA_PARTNER_OTHER})?"
145 ),
146 )
147 qb: Mapped[Optional[int]] = mapped_camcops_column(
148 permitted_value_checker=ONE_TO_TWO_CHECKER,
149 comment=(
150 f"Question B: Was the service type inpatient [mother-and-baby "
151 f"unit, MBU] ({VAL_QB_INPATIENT}) or "
152 f"community ({VAL_QB_COMMUNITY})?"
153 ),
154 )
156 q1a: Mapped[Optional[int]] = mapped_camcops_column(
157 permitted_value_checker=ONE_TO_FIVE_CHECKER,
158 comment=f"Q1A: mental health at first contact {_MH_KEY}",
159 )
160 q1b: Mapped[Optional[int]] = mapped_camcops_column(
161 permitted_value_checker=ONE_TO_FIVE_CHECKER,
162 comment=f"Q1B: mental health at discharge {_MH_KEY}",
163 )
165 q2a: Mapped[Optional[int]] = mapped_camcops_column(
166 permitted_value_checker=ONE_TO_FOUR_CHECKER,
167 comment=f"Q2a: staff didn't communicate with others {_AGREE_KEY}",
168 )
169 q2b: Mapped[Optional[int]] = mapped_camcops_column(
170 permitted_value_checker=ONE_TO_FOUR_CHECKER,
171 comment=f"Q2b: Staff gave right amount of support {_AGREE_KEY}",
172 )
173 q2c: Mapped[Optional[int]] = mapped_camcops_column(
174 permitted_value_checker=ONE_TO_FOUR_CHECKER,
175 comment=f"Q2c: Help not quick enough after referral {_AGREE_KEY}",
176 )
177 q2d: Mapped[Optional[int]] = mapped_camcops_column(
178 permitted_value_checker=ONE_TO_FOUR_CHECKER,
179 comment=f"Q2d: Staff listened/understood {_AGREE_KEY}",
180 )
182 q2e: Mapped[Optional[int]] = mapped_camcops_column(
183 permitted_value_checker=ONE_TO_FOUR_CHECKER,
184 comment=f"Q2e: Staff didn't involve pt enough {_AGREE_KEY}",
185 )
186 q2f: Mapped[Optional[int]] = mapped_camcops_column(
187 permitted_value_checker=ONE_TO_FOUR_CHECKER,
188 comment=f"Q2f: Service provided information {_AGREE_KEY}",
189 )
190 q2g: Mapped[Optional[int]] = mapped_camcops_column(
191 permitted_value_checker=ONE_TO_FOUR_CHECKER,
192 comment=f"Q2g: Staff not very sensitive to pt {_AGREE_KEY}",
193 )
194 q2h: Mapped[Optional[int]] = mapped_camcops_column(
195 permitted_value_checker=ONE_TO_FOUR_CHECKER,
196 comment=f"Q2h: Staff helped understanding of illness {_AGREE_KEY}",
197 )
199 q2i: Mapped[Optional[int]] = mapped_camcops_column(
200 permitted_value_checker=ONE_TO_FOUR_CHECKER,
201 comment=f"Q2i: Staff not very sensitive to baby {_AGREE_KEY}",
202 )
203 q2j: Mapped[Optional[int]] = mapped_camcops_column(
204 permitted_value_checker=ONE_TO_FOUR_CHECKER,
205 comment=f"Q2j: Staff helped confidence re baby {_AGREE_KEY}",
206 )
207 q2k: Mapped[Optional[int]] = mapped_camcops_column(
208 permitted_value_checker=ONE_TO_FOUR_CHECKER,
209 comment=f"Q2k: Service involved others helpfully {_AGREE_KEY}",
210 )
211 q2l: Mapped[Optional[int]] = mapped_camcops_column(
212 permitted_value_checker=ONE_TO_FOUR_CHECKER,
213 comment=f"Q2l: Would recommend service {_AGREE_KEY}",
214 )
216 q3a: Mapped[Optional[int]] = mapped_camcops_column(
217 permitted_value_checker=ONE_TO_FOUR_CHECKER,
218 comment=f"Q3a: MBU clean {_AGREE_KEY} {_INPATIENT_ONLY}",
219 )
220 q3b: Mapped[Optional[int]] = mapped_camcops_column(
221 permitted_value_checker=ONE_TO_FOUR_CHECKER,
222 comment=f"Q3b: MBU not a good place to recover "
223 f"{_AGREE_KEY} {_INPATIENT_ONLY}",
224 )
225 q3c: Mapped[Optional[int]] = mapped_camcops_column(
226 permitted_value_checker=ONE_TO_FOUR_CHECKER,
227 comment=f"Q3c: MBU did not provide helpful activities "
228 f"{_AGREE_KEY} {_INPATIENT_ONLY}",
229 )
230 q3d: Mapped[Optional[int]] = mapped_camcops_column(
231 permitted_value_checker=ONE_TO_FOUR_CHECKER,
232 comment=f"Q3d: MBU a good place for baby to be with pt "
233 f"{_AGREE_KEY} {_INPATIENT_ONLY}",
234 )
235 q3e: Mapped[Optional[int]] = mapped_camcops_column(
236 permitted_value_checker=ONE_TO_FOUR_CHECKER,
237 comment=f"Q3e: MBU supported contact with family/friends "
238 f"{_AGREE_KEY} {_INPATIENT_ONLY}",
239 )
240 q3f: Mapped[Optional[int]] = mapped_camcops_column(
241 permitted_value_checker=ONE_TO_FOUR_CHECKER,
242 comment=f"Q3f: Food not acceptable {_AGREE_KEY} {_INPATIENT_ONLY}",
243 )
245 general_comments: Mapped[Optional[str]] = mapped_column(
246 FN_GENERAL_COMMENTS, UnicodeText, comment="General comments"
247 )
248 future_participation: Mapped[Optional[int]] = mapped_camcops_column(
249 permitted_value_checker=ZERO_TO_ONE_CHECKER,
250 comment=f"Willing to participate in future studies "
251 f"({YES_INT} yes, {NO_INT} no)",
252 )
253 contact_details: Mapped[Optional[str]] = mapped_column(
254 FN_CONTACT_DETAILS, UnicodeText, comment="Contact details"
255 )
257 # -------------------------------------------------------------------------
258 # Fieldname collections
259 # -------------------------------------------------------------------------
260 REQUIRED_ALWAYS = [
261 FN_QA_RESPONDENT,
262 FN_QB_SERVICE_TYPE,
263 FN_Q1A_MH_FIRST_CONTACT,
264 FN_Q1B_MH_DISCHARGE,
265 FN_Q2A_STAFF_DID_NOT_COMMUNICATE,
266 FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT,
267 FN_Q2C_HELP_NOT_QUICK_ENOUGH,
268 FN_Q2D_STAFF_LISTENED,
269 FN_Q2E_STAFF_DID_NOT_INVOLVE_ME,
270 FN_Q2F_SERVICE_PROVIDED_INFO,
271 FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME,
272 FN_Q2H_STAFF_HELPED_ME_UNDERSTAND,
273 FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY,
274 FN_Q2J_STAFF_HELPED_MY_CONFIDENCE,
275 FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY,
276 FN_Q2L_I_WOULD_RECOMMEND_SERVICE,
277 # not FN_GENERAL_COMMENTS,
278 FN_FUTURE_PARTICIPATION,
279 # not FN_CONTACT_DETAILS,
280 ]
281 REQUIRED_INPATIENT = [
282 FN_Q3A_UNIT_CLEAN,
283 FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER,
284 FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES,
285 FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY,
286 FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT,
287 FN_Q3F_FOOD_NOT_ACCEPTABLE,
288 ]
289 Q1_FIELDS = [FN_Q1A_MH_FIRST_CONTACT, FN_Q1B_MH_DISCHARGE]
290 Q2_FIELDS = [
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 ]
304 Q3_FIELDS = REQUIRED_INPATIENT
306 @staticmethod
307 def longname(req: "CamcopsRequest") -> str:
308 _ = req.gettext
309 return _("Perinatal Patient-rated Outcome and Experience Measure")
311 def was_inpatient(self) -> bool:
312 return self.qb == self.VAL_QB_INPATIENT
314 def respondent_not_patient(self) -> bool:
315 return self.qa == self.VAL_QA_PARTNER_OTHER
317 def offering_participation(self) -> bool:
318 return self.future_participation == self.YES_INT
320 def is_complete(self) -> bool:
321 if self.any_fields_none(self.REQUIRED_ALWAYS):
322 return False
323 if self.was_inpatient() and self.any_fields_none(
324 self.REQUIRED_INPATIENT
325 ):
326 return False
327 if not self.field_contents_valid():
328 return False
329 return True
331 def get_qa_options(self, req: CamcopsRequest) -> List[str]:
332 options = [
333 self.wxstring(req, f"qa_a{o}")
334 for o in range(self.VAL_QA_PATIENT, self.VAL_QA_PARTNER_OTHER + 1)
335 ]
337 return options
339 def get_qb_options(self, req: CamcopsRequest) -> List[str]:
340 options = [
341 self.wxstring(req, f"qb_a{o}")
342 for o in range(self.VAL_QB_INPATIENT, self.VAL_QB_COMMUNITY + 1)
343 ]
345 return options
347 def get_q1_options(self, req: CamcopsRequest) -> List[str]:
348 options = [
349 self.wxstring(req, f"q1_a{o}")
350 for o in range(
351 self.VAL_Q1_VERY_WELL, self.VAL_Q1_EXTREMELY_UNWELL + 1
352 )
353 ]
355 return options
357 def get_agree_options(self, req: CamcopsRequest) -> List[str]:
358 options = [
359 self.wxstring(req, f"agreement_a{o}")
360 for o in range(
361 self.VAL_STRONGLY_AGREE, self.VAL_STRONGLY_DISAGREE + 1
362 )
363 ]
365 return options
367 @staticmethod
368 def get_yn_options(req: CamcopsRequest) -> List[str]:
369 return [req.sstring(SS.NO), req.sstring(SS.YES)]
371 def get_task_html(self, req: CamcopsRequest) -> str:
372 def loadvalues(
373 _dict: Dict[int, str], _first: int, _last: int, _xstringprefix: str
374 ) -> None:
375 for val in range(_first, _last + 1):
376 _dict[val] = (
377 f"{val} — {self.wxstring(req, f'{_xstringprefix}{val}')}"
378 )
380 respondent_dict = {} # type: Dict[int, str]
381 loadvalues(
382 respondent_dict,
383 self.VAL_QA_PATIENT,
384 self.VAL_QA_PARTNER_OTHER,
385 "qa_a",
386 )
387 service_dict = {} # type: Dict[int, str]
388 loadvalues(
389 service_dict, self.VAL_QB_INPATIENT, self.VAL_QB_COMMUNITY, "qb_a"
390 )
391 mh_dict = {} # type: Dict[int, str]
392 loadvalues(
393 mh_dict,
394 self.VAL_Q1_VERY_WELL,
395 self.VAL_Q1_EXTREMELY_UNWELL,
396 "q1_a",
397 )
398 agree_dict = {} # type: Dict[int, str]
399 loadvalues(
400 agree_dict,
401 self.VAL_STRONGLY_AGREE,
402 self.VAL_STRONGLY_DISAGREE,
403 "agreement_a",
404 )
406 q_a_list = [] # type: List[str]
408 def addqa(_fieldname: str, _valuedict: Dict[int, str]) -> None:
409 xstringname = _fieldname + "_q"
410 q_a_list.append(
411 tr_qa(
412 self.xstring(req, xstringname), # not wxstring
413 get_from_dict(_valuedict, getattr(self, _fieldname)),
414 )
415 )
417 def subheading(_xstringname: str) -> None:
418 q_a_list.append(
419 subheading_spanning_two_columns(
420 self.wxstring(req, _xstringname)
421 )
422 )
424 # Preamble
425 addqa(self.FN_QA_RESPONDENT, respondent_dict)
426 addqa(self.FN_QB_SERVICE_TYPE, service_dict)
427 # The bulk
428 subheading("q1_stem")
429 for fieldname in self.Q1_FIELDS:
430 addqa(fieldname, mh_dict)
431 subheading("q2_stem")
432 for fieldname in self.Q2_FIELDS:
433 addqa(fieldname, agree_dict)
434 if self.was_inpatient():
435 subheading("q3_stem")
436 for fieldname in self.Q3_FIELDS:
437 addqa(fieldname, agree_dict)
438 # General
439 q_a_list.append(
440 subheading_spanning_two_columns(req.sstring(SS.GENERAL))
441 )
442 q_a_list.append(
443 tr_qa(
444 self.wxstring(req, "general_comments_q"), self.general_comments
445 )
446 )
447 q_a_list.append(
448 tr_qa(
449 self.wxstring(req, "participation_q"),
450 get_yes_no_none(req, self.future_participation),
451 )
452 )
453 if self.offering_participation():
454 q_a_list.append(
455 tr_qa(
456 self.wxstring(req, "contact_details_q"),
457 self.contact_details,
458 )
459 )
461 q_a = "\n".join(q_a_list)
462 return f"""
463 <div class="{CssClass.SUMMARY}">
464 <table class="{CssClass.SUMMARY}">
465 {self.get_is_complete_tr(req)}
466 </table>
467 </div>
468 <table class="{CssClass.TASKDETAIL}">
469 <tr>
470 <th width="60%">Question</th>
471 <th width="40%">Answer</th>
472 </tr>
473 {q_a}
474 </table>
475 <div class="{CssClass.FOOTNOTES}">
476 </div>
477 """
479 # No SNOMED codes for Perinatal-POEM.
482# =============================================================================
483# Reports
484# =============================================================================
487class PerinatalPoemReportTableConfig(object):
488 def __init__(
489 self,
490 heading: str,
491 column_headings: List[str],
492 fieldnames: List[str],
493 min_answer: int = 0,
494 xstring_format: str = "{}_q",
495 ) -> None:
496 self.heading = heading
497 self.column_headings = column_headings
498 self.fieldnames = fieldnames
499 self.min_answer = min_answer
500 self.xstring_format = xstring_format
503class PerinatalPoemReportTable(object):
504 def __init__(
505 self,
506 req: "CamcopsRequest",
507 heading: str,
508 column_headings: List[str],
509 rows: List[List[str]],
510 ) -> None:
511 _ = req.gettext
512 self.heading = heading
514 common_headings = [_("Question"), _("Total responses")]
515 self.column_headings = common_headings + column_headings
516 self.rows = rows
519class PerinatalPoemReport(
520 DateTimeFilteredReportMixin, Report, PercentageSummaryReportMixin
521):
522 """
523 Provides a summary of each question, x% of people said each response etc.
524 Then a summary of the comments.
525 """
527 HTML_TAG_RE = re.compile(r"<[^>]+>")
529 def __init__(self, *args: Any, **kwargs: Any):
530 super().__init__(*args, **kwargs)
531 self.task = PerinatalPoem() # dummy task, never written to DB
533 @classproperty
534 def task_class(self) -> Type["Task"]:
535 return PerinatalPoem
537 # noinspection PyMethodParameters
538 @classproperty
539 def report_id(cls) -> str:
540 return "perinatal_poem"
542 @classmethod
543 def title(cls, req: "CamcopsRequest") -> str:
544 _ = req.gettext
545 return _("Perinatal-POEM — Question summaries")
547 # noinspection PyMethodParameters
548 @classproperty
549 def superuser_only(cls) -> bool:
550 return False
552 def render_html(self, req: "CamcopsRequest") -> Response:
553 return render_to_response(
554 "perinatal_poem_report.mako",
555 dict(
556 title=self.title(req),
557 report_id=self.report_id,
558 start_datetime=self.start_datetime,
559 end_datetime=self.end_datetime,
560 tables=self._get_html_tables(req),
561 comments=self._get_comments(req),
562 ),
563 request=req,
564 )
566 def get_spreadsheet_pages(
567 self, req: "CamcopsRequest"
568 ) -> List[SpreadsheetPage]:
569 _ = req.gettext
571 pages = []
573 for table in self._get_spreadsheet_tables(req):
574 pages.append(
575 self.get_spreadsheet_page(
576 name=table.heading,
577 column_names=table.column_headings,
578 rows=table.rows,
579 )
580 )
582 pages.append(
583 self.get_spreadsheet_page(
584 name=_("Comments"),
585 column_names=[_("Comment")],
586 rows=self._get_comment_rows(req),
587 )
588 )
590 return pages
592 def _get_html_tables(
593 self, req: "CamcopsRequest"
594 ) -> List["PerinatalPoemReportTable"]:
596 return [
597 self._get_html_table(req, config)
598 for config in self._get_table_configs(req)
599 ]
601 def _get_spreadsheet_tables(
602 self, req: "CamcopsRequest"
603 ) -> List["PerinatalPoemReportTable"]:
605 return [
606 self._get_spreadsheet_table(req, config)
607 for config in self._get_table_configs(req)
608 ]
610 def _get_table_configs(
611 self, req: "CamcopsRequest"
612 ) -> List["PerinatalPoemReportTableConfig"]:
613 return [
614 PerinatalPoemReportTableConfig(
615 heading=self.task.xstring(req, "qa_q"),
616 column_headings=self.task.get_qa_options(req),
617 fieldnames=["qa"],
618 min_answer=1,
619 ),
620 PerinatalPoemReportTableConfig(
621 heading=self.task.xstring(req, "qb_q"),
622 column_headings=self.task.get_qb_options(req),
623 fieldnames=["qb"],
624 min_answer=1,
625 ),
626 PerinatalPoemReportTableConfig(
627 heading=self.task.xstring(req, "q1_stem"),
628 column_headings=self.task.get_q1_options(req),
629 fieldnames=PerinatalPoem.Q1_FIELDS,
630 min_answer=1,
631 ),
632 PerinatalPoemReportTableConfig(
633 heading=self.task.xstring(req, "q2_stem"),
634 column_headings=self.task.get_agree_options(req),
635 fieldnames=PerinatalPoem.Q2_FIELDS,
636 min_answer=1,
637 ),
638 PerinatalPoemReportTableConfig(
639 heading=self.task.xstring(req, "q3_stem"),
640 column_headings=self.task.get_agree_options(req),
641 fieldnames=PerinatalPoem.Q3_FIELDS,
642 min_answer=1,
643 ),
644 PerinatalPoemReportTableConfig(
645 heading=self.task.xstring(req, "participation_q"),
646 column_headings=self.task.get_yn_options(req),
647 fieldnames=["future_participation"],
648 xstring_format="participation_q",
649 ),
650 ]
652 def _get_html_table(
653 self, req: "CamcopsRequest", config: PerinatalPoemReportTableConfig
654 ) -> PerinatalPoemReportTable:
655 column_dict = {}
657 for fieldname in config.fieldnames:
658 column_dict[fieldname] = self.task.xstring(
659 req, config.xstring_format.format(fieldname)
660 )
662 rows = self.get_percentage_summaries(
663 req,
664 column_dict=column_dict,
665 num_answers=len(config.column_headings),
666 cell_format="{0:.1f}%",
667 min_answer=config.min_answer,
668 )
670 return PerinatalPoemReportTable(
671 req,
672 heading=config.heading,
673 column_headings=config.column_headings,
674 rows=rows,
675 )
677 def _get_spreadsheet_table(
678 self, req: "CamcopsRequest", config: PerinatalPoemReportTableConfig
679 ) -> PerinatalPoemReportTable:
680 column_dict = {}
682 for fieldname in config.fieldnames:
683 column_dict[fieldname] = self._strip_tags(
684 self.task.xstring(req, config.xstring_format.format(fieldname))
685 )
687 rows = self.get_percentage_summaries(
688 req,
689 column_dict=column_dict,
690 num_answers=len(config.column_headings),
691 min_answer=config.min_answer,
692 )
694 return PerinatalPoemReportTable(
695 req,
696 heading=config.heading,
697 column_headings=config.column_headings,
698 rows=rows,
699 )
701 def _strip_tags(self, text: str) -> str:
702 return self.HTML_TAG_RE.sub("", text)
704 def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]:
705 """
706 A list of all the additional comments
707 """
709 wheres = [column("general_comments").isnot(None)]
711 self.add_task_report_filters(wheres) # type: ignore[arg-type]
713 # noinspection PyUnresolvedReferences
714 query: Select[Any] = (
715 select(column("general_comments"))
716 .select_from(self.task.__table__)
717 .where(and_(*wheres))
718 )
720 comment_rows = []
722 for result in req.dbsession.execute(query).fetchall():
723 comment_rows.append(result)
725 return comment_rows
727 def _get_comments(self, req: "CamcopsRequest") -> List[str]:
728 """
729 A list of all the additional comments.
730 """
731 return [x[0] for x in self._get_comment_rows(req)]