Coverage for tasks/khandaker_mojo_sociodemographics.py: 59%
78 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/khandaker_mojo_sociodemographics.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 Any, Optional, Type
30from sqlalchemy.sql.sqltypes import Integer, UnicodeText
32from camcops_server.cc_modules.cc_constants import CssClass
33from camcops_server.cc_modules.cc_html import tr_qa
34from camcops_server.cc_modules.cc_request import CamcopsRequest
35from camcops_server.cc_modules.cc_sqla_coltypes import (
36 camcops_column,
37 ZERO_TO_10_CHECKER,
38 ZERO_TO_FOUR_CHECKER,
39 ZERO_TO_SEVEN_CHECKER,
40 ZERO_TO_SIX_CHECKER,
41 ZERO_TO_TWO_CHECKER,
42)
43from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
46class KhandakerMojoSociodemographics( # type: ignore[misc]
47 TaskHasPatientMixin,
48 Task,
49):
50 """
51 Server implementation of the Khandaker_2_MOJOSociodemographics task
52 """
54 __tablename__ = "khandaker_mojo_sociodemographics"
55 shortname = "Khandaker_MOJO_Sociodemographics"
56 info_filename_stem = "khandaker_mojo"
57 provides_trackers = False
59 FN_GENDER = "gender"
60 FN_ETHNICITY = "ethnicity"
61 FN_WITH_WHOM_LIVE = "with_whom_live"
62 FN_RELATIONSHIP_STATUS = "relationship_status"
63 FN_EDUCATION = "education"
64 FN_EMPLOYMENT = "employment"
65 FN_ACCOMMODATION = "accommodation"
67 FN_OTHER_GENDER = "other_gender"
68 FN_OTHER_ETHNICITY = "other_ethnicity"
69 FN_OTHER_WITH_WHOM_LIVE = "other_with_whom_live"
70 FN_OTHER_EMPLOYMENT = "other_employment"
71 FN_OTHER_ACCOMMODATION = "other_accommodation"
73 MANDATORY_FIELD_NAMES = [
74 FN_GENDER,
75 FN_ETHNICITY,
76 FN_WITH_WHOM_LIVE,
77 FN_RELATIONSHIP_STATUS,
78 FN_EDUCATION,
79 FN_EMPLOYMENT,
80 FN_ACCOMMODATION,
81 ]
83 OTHER_ANSWER_VALUES = {
84 FN_GENDER: 2,
85 FN_ETHNICITY: 10,
86 FN_WITH_WHOM_LIVE: 7,
87 FN_EMPLOYMENT: 7,
88 FN_ACCOMMODATION: 6,
89 }
91 @classmethod
92 def extend_columns(
93 cls: Type["KhandakerMojoSociodemographics"], **kwargs: Any
94 ) -> None:
95 setattr(
96 cls,
97 cls.FN_GENDER,
98 camcops_column(
99 cls.FN_GENDER,
100 Integer,
101 permitted_value_checker=ZERO_TO_TWO_CHECKER,
102 comment=(
103 "Gender at birth (0 Male, 1 Female, 2 Other (specify))"
104 ),
105 ),
106 )
107 setattr(
108 cls,
109 cls.FN_OTHER_GENDER,
110 camcops_column(
111 cls.FN_OTHER_GENDER, UnicodeText, comment="Other (specify)"
112 ),
113 )
114 setattr(
115 cls,
116 cls.FN_ETHNICITY,
117 camcops_column(
118 cls.FN_ETHNICITY,
119 Integer,
120 permitted_value_checker=ZERO_TO_10_CHECKER,
121 comment=(
122 "Ethnicity (0 White, 1 Mixed, 2 Indian, 3 Pakistani, "
123 "4 Bangladeshi, 5 Other Asian, 6 Black Caribbean, "
124 "7 Black African, 8 Black Other, 9 Chinese, "
125 "10 Other (specify))"
126 ),
127 ),
128 )
129 setattr(
130 cls,
131 cls.FN_OTHER_ETHNICITY,
132 camcops_column(
133 cls.FN_OTHER_ETHNICITY, UnicodeText, comment="Other (specify)"
134 ),
135 )
136 setattr(
137 cls,
138 cls.FN_WITH_WHOM_LIVE,
139 camcops_column(
140 cls.FN_WITH_WHOM_LIVE,
141 Integer,
142 permitted_value_checker=ZERO_TO_SEVEN_CHECKER,
143 comment=(
144 "0 Alone, 1 Alone with children, 2 Partner/Spouse, "
145 "3 Partner/Spouse and children, 4 Parents, "
146 "5 Other family, 6 Friends, 7 Other (specify)"
147 ),
148 ),
149 )
150 setattr(
151 cls,
152 cls.FN_OTHER_WITH_WHOM_LIVE,
153 camcops_column(
154 cls.FN_OTHER_WITH_WHOM_LIVE,
155 UnicodeText,
156 comment="Other (specify)",
157 ),
158 )
159 setattr(
160 cls,
161 cls.FN_RELATIONSHIP_STATUS,
162 camcops_column(
163 cls.FN_RELATIONSHIP_STATUS,
164 Integer,
165 permitted_value_checker=ZERO_TO_FOUR_CHECKER,
166 comment=(
167 "0 Single, 1 Married / Civil partnership, "
168 "2 In steady relationship, 3 Divorced / separated, "
169 "4 Widowed"
170 ),
171 ),
172 )
173 setattr(
174 cls,
175 cls.FN_EDUCATION,
176 camcops_column(
177 cls.FN_EDUCATION,
178 Integer,
179 permitted_value_checker=ZERO_TO_FOUR_CHECKER,
180 comment=(
181 "0 No qualifications, 1 GCSE/O levels, 2 A levels, "
182 "3 Vocational/college (B. Tecs/NVQs etc), "
183 "4 University / Professional Qualifications"
184 ),
185 ),
186 )
187 setattr(
188 cls,
189 cls.FN_EMPLOYMENT,
190 camcops_column(
191 cls.FN_EMPLOYMENT,
192 Integer,
193 permitted_value_checker=ZERO_TO_SEVEN_CHECKER,
194 comment=(
195 "0 No unemployed, 1 No student, 2 Yes full time, "
196 "3 Yes part time, 4 Full time homemaker, "
197 "5 Self employed, 6 Not working for medical reasons, "
198 "7 Other (specify)"
199 ),
200 ),
201 )
202 setattr(
203 cls,
204 cls.FN_OTHER_EMPLOYMENT,
205 camcops_column(
206 cls.FN_OTHER_EMPLOYMENT, UnicodeText, comment="Other (specify)"
207 ),
208 )
209 setattr(
210 cls,
211 cls.FN_ACCOMMODATION,
212 camcops_column(
213 cls.FN_ACCOMMODATION,
214 Integer,
215 permitted_value_checker=ZERO_TO_SIX_CHECKER,
216 comment=(
217 "0 Own outright, 1 Own with mortgage, "
218 "2 Rent from local authority etc, "
219 "3 Rent from landlord (private), "
220 "4 Shared ownership (part rent, part mortgage)"
221 "5 Live rent free, 6 Other (specify)"
222 ),
223 ),
224 )
225 setattr(
226 cls,
227 cls.FN_OTHER_ACCOMMODATION,
228 camcops_column(
229 cls.FN_OTHER_ACCOMMODATION,
230 UnicodeText,
231 comment="Other (specify)",
232 ),
233 )
235 @staticmethod
236 def longname(req: "CamcopsRequest") -> str:
237 _ = req.gettext
238 return _("Khandaker GM — MOJO — Sociodemographics")
240 def is_complete(self) -> bool:
241 if self.any_fields_none(self.MANDATORY_FIELD_NAMES):
242 return False
244 if not self.field_contents_valid():
245 return False
247 for name, other_option in self.OTHER_ANSWER_VALUES.items():
248 if getattr(self, name) == other_option:
249 if getattr(self, f"other_{name}") is None:
250 return False
252 return True
254 def get_task_html(self, req: CamcopsRequest) -> str:
255 rows = ""
257 for field_name in self.MANDATORY_FIELD_NAMES:
258 question_text = self.xstring(req, f"q_{field_name}")
259 answer_text = self.get_answer_text(req, field_name)
261 rows += tr_qa(question_text, answer_text)
263 html = f"""
264 <div class="{CssClass.SUMMARY}">
265 <table class="{CssClass.SUMMARY}">
266 {self.get_is_complete_tr(req)}
267 </table>
268 </div>
269 <table class="{CssClass.TASKDETAIL}">
270 <tr>
271 <th width="60%">Question</th>
272 <th width="40%">Answer</th>
273 </tr>
274 {rows}
275 </table>
276 """
278 return html
280 def get_answer_text(
281 self, req: CamcopsRequest, field_name: str
282 ) -> Optional[str]:
283 answer = getattr(self, field_name)
285 if answer is None:
286 return answer
288 answer_text = self.xstring(req, f"{field_name}_option{answer}")
290 if self.answered_other(field_name):
291 other_answer = getattr(self, f"other_{field_name}")
293 if not other_answer:
294 other_answer = "?"
296 answer_text = f"{answer_text} — {other_answer}"
298 return f"{answer} — {answer_text}"
300 def answered_other(self, field_name: str) -> bool:
301 if field_name not in self.OTHER_ANSWER_VALUES:
302 return False
304 other_option = self.OTHER_ANSWER_VALUES[field_name]
306 return getattr(self, field_name) == other_option