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

1""" 

2camcops_server/tasks/khandaker_mojo_sociodemographics.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

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. 

15 

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. 

20 

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/>. 

23 

24=============================================================================== 

25 

26""" 

27 

28from typing import Any, Optional, Type 

29 

30from sqlalchemy.sql.sqltypes import Integer, UnicodeText 

31 

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 

44 

45 

46class KhandakerMojoSociodemographics( # type: ignore[misc] 

47 TaskHasPatientMixin, 

48 Task, 

49): 

50 """ 

51 Server implementation of the Khandaker_2_MOJOSociodemographics task 

52 """ 

53 

54 __tablename__ = "khandaker_mojo_sociodemographics" 

55 shortname = "Khandaker_MOJO_Sociodemographics" 

56 info_filename_stem = "khandaker_mojo" 

57 provides_trackers = False 

58 

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" 

66 

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" 

72 

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 ] 

82 

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 } 

90 

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 ) 

234 

235 @staticmethod 

236 def longname(req: "CamcopsRequest") -> str: 

237 _ = req.gettext 

238 return _("Khandaker GM — MOJO — Sociodemographics") 

239 

240 def is_complete(self) -> bool: 

241 if self.any_fields_none(self.MANDATORY_FIELD_NAMES): 

242 return False 

243 

244 if not self.field_contents_valid(): 

245 return False 

246 

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 

251 

252 return True 

253 

254 def get_task_html(self, req: CamcopsRequest) -> str: 

255 rows = "" 

256 

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) 

260 

261 rows += tr_qa(question_text, answer_text) 

262 

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 """ 

277 

278 return html 

279 

280 def get_answer_text( 

281 self, req: CamcopsRequest, field_name: str 

282 ) -> Optional[str]: 

283 answer = getattr(self, field_name) 

284 

285 if answer is None: 

286 return answer 

287 

288 answer_text = self.xstring(req, f"{field_name}_option{answer}") 

289 

290 if self.answered_other(field_name): 

291 other_answer = getattr(self, f"other_{field_name}") 

292 

293 if not other_answer: 

294 other_answer = "?" 

295 

296 answer_text = f"{answer_text} — {other_answer}" 

297 

298 return f"{answer} — {answer_text}" 

299 

300 def answered_other(self, field_name: str) -> bool: 

301 if field_name not in self.OTHER_ANSWER_VALUES: 

302 return False 

303 

304 other_option = self.OTHER_ANSWER_VALUES[field_name] 

305 

306 return getattr(self, field_name) == other_option