Coverage for tasks/gmcpq.py: 45%
105 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/gmcpq.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"""
28from typing import Optional
30import cardinal_pythonlib.rnc_web as ws
31from sqlalchemy.orm import Mapped, mapped_column
32from sqlalchemy.sql.sqltypes import Integer, UnicodeText
34from camcops_server.cc_modules.cc_constants import (
35 CssClass,
36 POSSIBLE_SEX_VALUES,
37)
38from camcops_server.cc_modules.cc_html import (
39 get_yes_no_none,
40 subheading_spanning_two_columns,
41 td,
42 tr,
43 tr_qa,
44)
45from camcops_server.cc_modules.cc_request import CamcopsRequest
46from camcops_server.cc_modules.cc_sqla_coltypes import (
47 BIT_CHECKER,
48 mapped_camcops_column,
49 ONE_TO_FIVE_CHECKER,
50 PermittedValueChecker,
51 ZERO_TO_FIVE_CHECKER,
52)
53from camcops_server.cc_modules.cc_sqla_coltypes import SexColType
54from camcops_server.cc_modules.cc_task import get_from_dict, Task
55from camcops_server.cc_modules.cc_text import SS
58# =============================================================================
59# GMCPQ
60# =============================================================================
63class GMCPQ(Task):
64 """
65 Server implementation of the GMC-PQ task.
66 """
68 __tablename__ = "gmcpq"
69 shortname = "GMC-PQ"
71 RATING_TEXT = " (1 poor - 5 very good, 0 does not apply)"
72 AGREE_TEXT = " (1 strongly disagree - 5 strongly agree, 0 does not apply)"
74 doctor: Mapped[Optional[str]] = mapped_column(
75 UnicodeText, comment="Doctor's name"
76 )
77 q1: Mapped[Optional[int]] = mapped_camcops_column(
78 permitted_value_checker=PermittedValueChecker(minimum=1, maximum=4),
79 comment="Filling in questionnaire for... (1 yourself, "
80 "2 child, 3 spouse/partner, 4 other relative/friend)",
81 )
82 q2a: Mapped[Optional[int]] = mapped_camcops_column(
83 permitted_value_checker=BIT_CHECKER,
84 comment="Reason: advice? (0 no, 1 yes)",
85 )
86 q2b: Mapped[Optional[int]] = mapped_camcops_column(
87 permitted_value_checker=BIT_CHECKER,
88 comment="Reason: one-off problem? (0 no, 1 yes)",
89 )
90 q2c: Mapped[Optional[int]] = mapped_camcops_column(
91 permitted_value_checker=BIT_CHECKER,
92 comment="Reason: ongoing problem? (0 no, 1 yes)",
93 )
94 q2d: Mapped[Optional[int]] = mapped_camcops_column(
95 permitted_value_checker=BIT_CHECKER,
96 comment="Reason: routine check? (0 no, 1 yes)",
97 )
98 q2e: Mapped[Optional[int]] = mapped_camcops_column(
99 permitted_value_checker=BIT_CHECKER,
100 comment="Reason: treatment? (0 no, 1 yes)",
101 )
102 q2f: Mapped[Optional[int]] = mapped_camcops_column(
103 permitted_value_checker=BIT_CHECKER,
104 comment="Reason: other? (0 no, 1 yes)",
105 )
106 q2f_details: Mapped[Optional[str]] = mapped_column(
107 UnicodeText, comment="Reason, other, details"
108 )
109 q3: Mapped[Optional[int]] = mapped_camcops_column(
110 permitted_value_checker=ONE_TO_FIVE_CHECKER,
111 comment="How important to health/wellbeing was the reason "
112 "(1 not very - 5 very)",
113 )
114 q4a: Mapped[Optional[int]] = mapped_camcops_column(
115 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
116 comment="How good: being polite" + RATING_TEXT,
117 )
118 q4b: Mapped[Optional[int]] = mapped_camcops_column(
119 "q4b",
120 Integer,
121 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
122 comment="How good: making you feel at ease" + RATING_TEXT,
123 )
124 q4c: Mapped[Optional[int]] = mapped_camcops_column(
125 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
126 comment="How good: listening" + RATING_TEXT,
127 )
128 q4d: Mapped[Optional[int]] = mapped_camcops_column(
129 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
130 comment="How good: assessing medical condition" + RATING_TEXT,
131 )
132 q4e: Mapped[Optional[int]] = mapped_camcops_column(
133 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
134 comment="How good: explaining" + RATING_TEXT,
135 )
136 q4f: Mapped[Optional[int]] = mapped_camcops_column(
137 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
138 comment="How good: involving you in decisions" + RATING_TEXT,
139 )
140 q4g: Mapped[Optional[int]] = mapped_camcops_column(
141 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
142 comment="How good: providing/arranging treatment" + RATING_TEXT,
143 )
144 q5a: Mapped[Optional[int]] = mapped_camcops_column(
145 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
146 comment="Agree/disagree: will keep info confidential" + AGREE_TEXT,
147 )
148 q5b: Mapped[Optional[int]] = mapped_camcops_column(
149 permitted_value_checker=ZERO_TO_FIVE_CHECKER,
150 comment="Agree/disagree: honest/trustworthy" + AGREE_TEXT,
151 )
152 q6: Mapped[Optional[int]] = mapped_camcops_column(
153 permitted_value_checker=BIT_CHECKER,
154 comment="Confident in doctor's ability to provide care (0 no, 1 yes)",
155 )
156 q7: Mapped[Optional[int]] = mapped_camcops_column(
157 permitted_value_checker=BIT_CHECKER,
158 comment="Would be completely happy to see this doctor again "
159 "(0 no, 1 yes)",
160 )
161 q8: Mapped[Optional[int]] = mapped_camcops_column(
162 permitted_value_checker=BIT_CHECKER,
163 comment="Was this visit with your usual doctor (0 no, 1 yes)",
164 )
165 q9: Mapped[Optional[str]] = mapped_column(
166 UnicodeText, comment="Other comments"
167 )
168 q10: Mapped[Optional[str]] = mapped_camcops_column(
169 SexColType,
170 permitted_value_checker=PermittedValueChecker(
171 permitted_values=POSSIBLE_SEX_VALUES
172 ),
173 comment="Sex of rater (M, F, X)",
174 )
175 q11: Mapped[Optional[int]] = mapped_camcops_column(
176 permitted_value_checker=ONE_TO_FIVE_CHECKER,
177 comment="Age (1 = under 15, 2 = 15-20, 3 = 21-40, "
178 "4 = 40-60, 5 = 60 or over", # yes, I know it's daft
179 )
180 q12: Mapped[Optional[int]] = mapped_camcops_column(
181 permitted_value_checker=PermittedValueChecker(minimum=1, maximum=16),
182 comment="Ethnicity (1 = White British, 2 = White Irish, "
183 "3 = White other, 4 = Mixed W/B Caribbean, "
184 "5 = Mixed W/B African, 6 = Mixed W/Asian, 7 = Mixed other, "
185 "8 = Asian/Asian British - Indian, 9 = A/AB - Pakistani, "
186 "10 = A/AB - Bangladeshi, 11 = A/AB - other, "
187 "12 = Black/Black British - Caribbean, 13 = B/BB - African, "
188 "14 = B/BB - other, 15 = Chinese, 16 = other)",
189 )
190 q12_details: Mapped[Optional[str]] = mapped_column(
191 UnicodeText, comment="Ethnic group, other, details"
192 )
194 @staticmethod
195 def longname(req: "CamcopsRequest") -> str:
196 _ = req.gettext
197 return _("GMC Patient Questionnaire")
199 def is_complete(self) -> bool:
200 return (
201 self.is_field_not_none("q1")
202 and self.is_field_not_none("q3")
203 and self.is_field_not_none("q4a")
204 and self.is_field_not_none("q4b")
205 and self.is_field_not_none("q4c")
206 and self.is_field_not_none("q4d")
207 and self.is_field_not_none("q4e")
208 and self.is_field_not_none("q4f")
209 and self.is_field_not_none("q4g")
210 and self.is_field_not_none("q5a")
211 and self.is_field_not_none("q5b")
212 and self.is_field_not_none("q6")
213 and self.is_field_not_none("q7")
214 and self.is_field_not_none("q8")
215 and self.field_contents_valid()
216 )
218 def get_task_html(self, req: CamcopsRequest) -> str:
219 dict_q1: dict[Optional[int], Optional[str]] = {None: None}
220 dict_q3: dict[Optional[int], Optional[str]] = {None: None}
221 dict_q4: dict[Optional[int], Optional[str]] = {None: None}
222 dict_q5: dict[Optional[int], Optional[str]] = {None: None}
223 dict_q11: dict[Optional[int], Optional[str]] = {None: None}
224 dict_q12: dict[Optional[int], Optional[str]] = {None: None}
225 for option in range(1, 5):
226 dict_q1[option] = self.wxstring(req, "q1_option" + str(option))
227 for option in range(1, 6):
228 dict_q3[option] = self.wxstring(req, "q3_option" + str(option))
229 dict_q11[option] = self.wxstring(req, "q11_option" + str(option))
230 for option in range(0, 6):
231 prefix = str(option) + " – " if option > 0 else ""
232 dict_q4[option] = prefix + self.wxstring(
233 req, "q4_option" + str(option)
234 )
235 dict_q5[option] = prefix + self.wxstring(
236 req, "q5_option" + str(option)
237 )
238 for option in range(1, 17):
239 dict_q12[option] = self.wxstring(
240 req, "ethnicity_option" + str(option)
241 )
242 h = f"""
243 <div class="{CssClass.SUMMARY}">
244 <table class="{CssClass.SUMMARY}">
245 {self.get_is_complete_tr(req)}
246 </table>
247 </div>
248 <table class="{CssClass.TASKDETAIL}">
249 <tr>
250 <th width="60%">Question</th>
251 <th width="40%">Answer</th>
252 </tr>
253 """
254 ell = "… " # horizontal ellipsis
255 sep_row = subheading_spanning_two_columns("<br>")
256 blank_cell = td("", td_class=CssClass.SUBHEADING)
257 h += tr_qa(self.wxstring(req, "q_doctor"), ws.webify(self.doctor))
258 h += sep_row
259 h += tr_qa(self.wxstring(req, "q1"), get_from_dict(dict_q1, self.q1))
260 h += tr(td(self.wxstring(req, "q2")), blank_cell, literal=True)
261 h += tr_qa(
262 ell + self.wxstring(req, "q2_a"),
263 get_yes_no_none(req, self.q2a),
264 default="",
265 )
266 h += tr_qa(
267 ell + self.wxstring(req, "q2_b"),
268 get_yes_no_none(req, self.q2b),
269 default="",
270 )
271 h += tr_qa(
272 ell + self.wxstring(req, "q2_c"),
273 get_yes_no_none(req, self.q2c),
274 default="",
275 )
276 h += tr_qa(
277 ell + self.wxstring(req, "q2_d"),
278 get_yes_no_none(req, self.q2d),
279 default="",
280 )
281 h += tr_qa(
282 ell + self.wxstring(req, "q2_e"),
283 get_yes_no_none(req, self.q2e),
284 default="",
285 )
286 h += tr_qa(
287 ell + self.wxstring(req, "q2_f"),
288 get_yes_no_none(req, self.q2f),
289 default="",
290 )
291 h += tr_qa(
292 ell + ell + self.wxstring(req, "q2f_s"),
293 ws.webify(self.q2f_details),
294 )
295 h += tr_qa(self.wxstring(req, "q3"), get_from_dict(dict_q3, self.q3))
296 h += tr(td(self.wxstring(req, "q4")), blank_cell, literal=True)
297 h += tr_qa(
298 ell + self.wxstring(req, "q4_a"), get_from_dict(dict_q4, self.q4a)
299 )
300 h += tr_qa(
301 ell + self.wxstring(req, "q4_b"), get_from_dict(dict_q4, self.q4b)
302 )
303 h += tr_qa(
304 ell + self.wxstring(req, "q4_c"), get_from_dict(dict_q4, self.q4c)
305 )
306 h += tr_qa(
307 ell + self.wxstring(req, "q4_d"), get_from_dict(dict_q4, self.q4d)
308 )
309 h += tr_qa(
310 ell + self.wxstring(req, "q4_e"), get_from_dict(dict_q4, self.q4e)
311 )
312 h += tr_qa(
313 ell + self.wxstring(req, "q4_f"), get_from_dict(dict_q4, self.q4f)
314 )
315 h += tr_qa(
316 ell + self.wxstring(req, "q4_g"), get_from_dict(dict_q4, self.q4g)
317 )
318 h += tr(td(self.wxstring(req, "q5")), blank_cell, literal=True)
319 h += tr_qa(
320 ell + self.wxstring(req, "q5_a"), get_from_dict(dict_q5, self.q5a)
321 )
322 h += tr_qa(
323 ell + self.wxstring(req, "q5_b"), get_from_dict(dict_q5, self.q5b)
324 )
325 h += tr_qa(self.wxstring(req, "q6"), get_yes_no_none(req, self.q6))
326 h += tr_qa(self.wxstring(req, "q7"), get_yes_no_none(req, self.q7))
327 h += tr_qa(self.wxstring(req, "q8"), get_yes_no_none(req, self.q8))
328 h += tr_qa(self.wxstring(req, "q9_s"), ws.webify(self.q9))
329 h += sep_row
330 h += tr_qa(req.sstring(SS.SEX), ws.webify(self.q10))
331 h += tr_qa(
332 self.wxstring(req, "q11"), get_from_dict(dict_q11, self.q11)
333 )
334 h += tr_qa(
335 self.wxstring(req, "q12"), get_from_dict(dict_q12, self.q12)
336 )
337 h += tr_qa(
338 ell + self.wxstring(req, "ethnicity_other_s"),
339 ws.webify(self.q12_details),
340 )
341 h += """
342 </table>
343 """
344 return h