Coverage for tasks/gmcpq.py : 45%

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