Coverage for tasks/apeqpt.py: 48%
61 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/apeqpt.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- By Joe Kearney, Rudolf Cardinal.
28"""
30from typing import Dict, List, Optional
32from pendulum import DateTime as Pendulum
33from sqlalchemy.orm import Mapped
34from sqlalchemy.sql.sqltypes import UnicodeText
36from camcops_server.cc_modules.cc_constants import CssClass
37from camcops_server.cc_modules.cc_fhir import (
38 FHIRAnsweredQuestion,
39 FHIRAnswerType,
40 FHIRQuestionType,
41)
42from camcops_server.cc_modules.cc_html import tr_qa
43from camcops_server.cc_modules.cc_request import CamcopsRequest
44from camcops_server.cc_modules.cc_sqla_coltypes import (
45 mapped_camcops_column,
46 PendulumDateTimeAsIsoTextColType,
47 ZERO_TO_ONE_CHECKER,
48 ZERO_TO_TWO_CHECKER,
49 ZERO_TO_FOUR_CHECKER,
50)
51from camcops_server.cc_modules.cc_summaryelement import SummaryElement
52from camcops_server.cc_modules.cc_task import get_from_dict, Task
55# =============================================================================
56# APEQPT
57# =============================================================================
60class Apeqpt(Task):
61 """
62 Server implementation of the APEQPT task.
63 """
65 __tablename__ = "apeqpt"
66 shortname = "APEQPT"
67 provides_trackers = True
69 # todo: remove q_datetime (here and in the C++) -- it duplicates when_created # noqa
70 q_datetime: Mapped[Optional[Pendulum]] = mapped_camcops_column(
71 PendulumDateTimeAsIsoTextColType,
72 comment="Date/time the assessment tool was completed",
73 )
75 N_CHOICE_QUESTIONS = 3
76 q1_choice: Mapped[Optional[int]] = mapped_camcops_column(
77 comment="Enough information was provided (0 no, 1 yes)",
78 permitted_value_checker=ZERO_TO_ONE_CHECKER,
79 )
80 q2_choice: Mapped[Optional[int]] = mapped_camcops_column(
81 comment="Treatment preference (0 no, 1 yes)",
82 permitted_value_checker=ZERO_TO_ONE_CHECKER,
83 )
84 q3_choice: Mapped[Optional[int]] = mapped_camcops_column(
85 comment="Preference offered (0 no, 1 yes, 2 N/A)",
86 permitted_value_checker=ZERO_TO_TWO_CHECKER,
87 )
89 q1_satisfaction: Mapped[Optional[int]] = mapped_camcops_column(
90 comment=(
91 "Patient satisfaction (0 not at all satisfied - "
92 "4 completely satisfied)"
93 ),
94 permitted_value_checker=ZERO_TO_FOUR_CHECKER,
95 )
96 q2_satisfaction: Mapped[Optional[str]] = mapped_camcops_column(
97 UnicodeText, comment="Service experience"
98 )
100 MAIN_QUESTIONS = [
101 "q_datetime",
102 "q1_choice",
103 "q2_choice",
104 "q3_choice",
105 "q1_satisfaction",
106 ]
108 @staticmethod
109 def longname(req: "CamcopsRequest") -> str:
110 _ = req.gettext
111 return _(
112 "Assessment Patient Experience Questionnaire "
113 "for Psychological Therapies"
114 )
116 def is_complete(self) -> bool:
117 if self.any_fields_none(self.MAIN_QUESTIONS):
118 return False
119 if not self.field_contents_valid():
120 return False
121 return True
123 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
124 return self.standard_task_summary_fields()
126 def get_task_html(self, req: CamcopsRequest) -> str:
127 c_dict = {
128 0: "0 — " + self.wxstring(req, "a0_choice"),
129 1: "1 — " + self.wxstring(req, "a1_choice"),
130 2: "2 — " + self.wxstring(req, "a2_choice"),
131 }
132 s_dict = {
133 0: "0 — " + self.wxstring(req, "a0_satisfaction"),
134 1: "1 — " + self.wxstring(req, "a1_satisfaction"),
135 2: "2 — " + self.wxstring(req, "a2_satisfaction"),
136 3: "3 — " + self.wxstring(req, "a3_satisfaction"),
137 4: "4 — " + self.wxstring(req, "a4_satisfaction"),
138 }
139 q_a = ""
140 for i in range(1, self.N_CHOICE_QUESTIONS + 1):
141 nstr = str(i)
142 q_a += tr_qa(
143 self.wxstring(req, "q" + nstr + "_choice"),
144 get_from_dict(c_dict, getattr(self, "q" + nstr + "_choice")),
145 )
147 q_a += tr_qa(
148 self.wxstring(req, "q1_satisfaction"),
149 get_from_dict(s_dict, self.q1_satisfaction),
150 )
151 q_a += tr_qa(
152 self.wxstring(req, "q2_satisfaction"),
153 self.q2_satisfaction,
154 default="",
155 )
157 return f"""
158 <div class="{CssClass.SUMMARY}">
159 <table class="{CssClass.SUMMARY}">
160 {self.get_is_complete_tr(req)}
161 </table>
162 </div>
163 <div class="{CssClass.EXPLANATION}">
164 Patient satisfaction rating for service provided. The service
165 is rated on choice offered and general satisfaction.
166 </div>
167 <table class="{CssClass.TASKDETAIL}">
168 <tr>
169 <th width="60%">Question</th>
170 <th width="40%">Answer</th>
171 </tr>
172 {q_a}
173 </table>
174 """
176 def get_fhir_questionnaire(
177 self, req: "CamcopsRequest"
178 ) -> List[FHIRAnsweredQuestion]:
179 items = [] # type: List[FHIRAnsweredQuestion]
181 yes_no_options = {} # type: Dict[int, str]
182 for index in range(2):
183 yes_no_options[index] = self.wxstring(req, f"a{index}_choice")
184 items.append(
185 FHIRAnsweredQuestion(
186 qname="q1_choice",
187 qtext=self.wxstring(req, "q1_choice"),
188 qtype=FHIRQuestionType.CHOICE,
189 answer_type=FHIRAnswerType.INTEGER,
190 answer=self.q1_choice,
191 answer_options=yes_no_options,
192 )
193 )
194 items.append(
195 FHIRAnsweredQuestion(
196 qname="q2_choice",
197 qtext=self.wxstring(req, "q2_choice"),
198 qtype=FHIRQuestionType.CHOICE,
199 answer_type=FHIRAnswerType.INTEGER,
200 answer=self.q2_choice,
201 answer_options=yes_no_options,
202 )
203 )
205 yes_no_na_options = yes_no_options.copy()
206 yes_no_na_options[2] = self.wxstring(req, "a2_choice")
207 items.append(
208 FHIRAnsweredQuestion(
209 qname="q3_choice",
210 qtext=self.wxstring(req, "q3_choice"),
211 qtype=FHIRQuestionType.CHOICE,
212 answer_type=FHIRAnswerType.INTEGER,
213 answer=self.q3_choice,
214 answer_options=yes_no_na_options,
215 )
216 )
218 satisfaction_options = {} # type: Dict[int, str]
219 for index in range(5):
220 satisfaction_options[index] = self.wxstring(
221 req, f"a{index}_satisfaction"
222 )
223 items.append(
224 FHIRAnsweredQuestion(
225 qname="q1_satisfaction",
226 qtext=self.xstring(req, "q1_satisfaction"),
227 qtype=FHIRQuestionType.CHOICE,
228 answer_type=FHIRAnswerType.INTEGER,
229 answer=self.q1_satisfaction,
230 answer_options=satisfaction_options,
231 )
232 )
234 items.append(
235 FHIRAnsweredQuestion(
236 qname="q2_satisfaction",
237 qtext=self.xstring(req, "q2_satisfaction"),
238 qtype=FHIRQuestionType.STRING,
239 answer_type=FHIRAnswerType.STRING,
240 answer=self.q2_satisfaction,
241 )
242 )
244 return items