Coverage for tasks/dad.py: 40%

98 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/dad.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, Dict, Iterable, List, Tuple, Type 

29 

30from sqlalchemy.sql.sqltypes import Integer 

31 

32from camcops_server.cc_modules.cc_constants import ( 

33 CssClass, 

34 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

35) 

36from camcops_server.cc_modules.cc_html import ( 

37 answer, 

38 subheading_spanning_two_columns, 

39 tr, 

40) 

41from camcops_server.cc_modules.cc_request import CamcopsRequest 

42from camcops_server.cc_modules.cc_sqla_coltypes import ( 

43 camcops_column, 

44 PermittedValueChecker, 

45) 

46from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

47from camcops_server.cc_modules.cc_task import ( 

48 Task, 

49 TaskHasClinicianMixin, 

50 TaskHasPatientMixin, 

51 TaskHasRespondentMixin, 

52) 

53 

54YES = 1 

55NO = 0 

56NA = -99 

57YN_NA_CHECKER = PermittedValueChecker(permitted_values=[YES, NO, NA]) 

58 

59 

60# ============================================================================= 

61# DAD 

62# ============================================================================= 

63 

64 

65class Dad( # type: ignore[misc] 

66 TaskHasPatientMixin, 

67 TaskHasRespondentMixin, 

68 TaskHasClinicianMixin, 

69 Task, 

70): 

71 """ 

72 Server implementation of the DAD task. 

73 """ 

74 

75 __tablename__ = "dad" 

76 shortname = "DAD" 

77 

78 GROUPS = [ 

79 "hygiene", 

80 "dressing", 

81 "continence", 

82 "eating", 

83 "mealprep", 

84 "telephone", 

85 "outing", 

86 "finance", 

87 "medications", 

88 "leisure", 

89 ] 

90 ITEMS = [ 

91 "hygiene_init_wash", 

92 "hygiene_init_teeth", 

93 "hygiene_init_hair", 

94 "hygiene_plan_wash", 

95 "hygiene_exec_wash", 

96 "hygiene_exec_hair", 

97 "hygiene_exec_teeth", 

98 "dressing_init_dress", 

99 "dressing_plan_clothing", 

100 "dressing_plan_order", 

101 "dressing_exec_dress", 

102 "dressing_exec_undress", 

103 "continence_init_toilet", 

104 "continence_exec_toilet", 

105 "eating_init_eat", 

106 "eating_plan_utensils", 

107 "eating_exec_eat", 

108 "mealprep_init_meal", 

109 "mealprep_plan_meal", 

110 "mealprep_exec_meal", 

111 "telephone_init_phone", 

112 "telephone_plan_dial", 

113 "telephone_exec_conversation", 

114 "telephone_exec_message", 

115 "outing_init_outing", 

116 "outing_plan_outing", 

117 "outing_exec_reach_destination", 

118 "outing_exec_mode_transportation", 

119 "outing_exec_return_with_shopping", 

120 "finance_init_interest", 

121 "finance_plan_pay_bills", 

122 "finance_plan_organise_correspondence", 

123 "finance_exec_handle_money", 

124 "medications_init_medication", 

125 "medications_exec_take_medications", 

126 "leisure_init_interest_leisure", 

127 "leisure_init_interest_chores", 

128 "leisure_plan_chores", 

129 "leisure_exec_complete_chores", 

130 "leisure_exec_safe_at_home", 

131 ] 

132 

133 @classmethod 

134 def extend_columns(cls: Type["Dad"], **kwargs: Any) -> None: 

135 explan = f" ({YES} yes, {NO} no, {NA} not applicable)" 

136 for colname in cls.ITEMS: 

137 setattr( 

138 cls, 

139 colname, 

140 camcops_column( 

141 colname, 

142 Integer, 

143 permitted_value_checker=YN_NA_CHECKER, 

144 comment=colname + explan, 

145 ), 

146 ) 

147 

148 @staticmethod 

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

150 _ = req.gettext 

151 return _("Disability Assessment for Dementia") 

152 

153 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

154 d = self.get_score_dict() 

155 s = self.standard_task_summary_fields() 

156 for item in d: 

157 s.extend( 

158 [ 

159 SummaryElement( 

160 name=item + "_n", 

161 coltype=Integer(), 

162 value=d[item][0], 

163 comment=item + " (numerator)", 

164 ), 

165 SummaryElement( 

166 name=item + "_d", 

167 coltype=Integer(), 

168 value=d[item][1], 

169 comment=item + " (denominator)", 

170 ), 

171 ] 

172 ) 

173 return s 

174 

175 # noinspection PyMethodOverriding 

176 @staticmethod 

177 def is_complete() -> bool: 

178 return True 

179 

180 @classmethod 

181 def get_items_activity(cls, activity: str) -> List[str]: 

182 return [item for item in cls.ITEMS if item.startswith(activity)] 

183 

184 @classmethod 

185 def get_items_activities(cls, activities: Iterable[str]) -> List[str]: 

186 return [ 

187 item 

188 for item in cls.ITEMS 

189 if any(item.startswith(activity) for activity in activities) 

190 ] 

191 

192 @classmethod 

193 def get_items_phase(cls, phase: str) -> List[str]: 

194 return [item for item in cls.ITEMS if phase in item] 

195 

196 def get_score(self, fields: List[str]) -> Tuple[int, int]: 

197 score = self.count_where(fields, [YES]) 

198 possible = self.count_wherenot(fields, [None, NA]) 

199 return score, possible 

200 

201 def get_score_dict(self) -> Dict: 

202 total = self.get_score(self.ITEMS) 

203 hygiene = self.get_score(self.get_items_activity("hygiene")) 

204 dressing = self.get_score(self.get_items_activity("dressing")) 

205 continence = self.get_score(self.get_items_activity("continence")) 

206 eating = self.get_score(self.get_items_activity("eating")) 

207 badl = self.get_score( 

208 self.get_items_activities( 

209 ["hygiene", "dressing", "continence", "eating"] 

210 ) 

211 ) 

212 mealprep = self.get_score(self.get_items_activity("mealprep")) 

213 telephone = self.get_score(self.get_items_activity("telephone")) 

214 outing = self.get_score(self.get_items_activity("outing")) 

215 finance = self.get_score(self.get_items_activity("finance")) 

216 medications = self.get_score(self.get_items_activity("medications")) 

217 leisure = self.get_score(self.get_items_activity("leisure")) 

218 iadl = self.get_score( 

219 self.get_items_activities( 

220 [ 

221 "mealprep", 

222 "telephone", 

223 "outing", 

224 "finance", 

225 "medications", 

226 "leisure", 

227 ] 

228 ) 

229 ) 

230 initiation = self.get_score(self.get_items_phase("init")) 

231 planning = self.get_score(self.get_items_phase("plan")) 

232 execution = self.get_score(self.get_items_phase("exec")) 

233 # n for numerator, d for denominator 

234 return dict( 

235 total=total, 

236 hygiene=hygiene, 

237 dressing=dressing, 

238 continence=continence, 

239 eating=eating, 

240 badl=badl, 

241 mealprep=mealprep, 

242 telephone=telephone, 

243 outing=outing, 

244 finance=finance, 

245 medications=medications, 

246 leisure=leisure, 

247 iadl=iadl, 

248 initiation=initiation, 

249 planning=planning, 

250 execution=execution, 

251 ) 

252 

253 @staticmethod 

254 def report_score(score_tuple: Tuple[int, int]) -> str: 

255 return f"{answer(score_tuple[0])} / {score_tuple[1]}" 

256 

257 def report_answer(self, field: str) -> str: 

258 value = getattr(self, field) 

259 if value == YES: 

260 text = "Yes (1)" 

261 elif value == NO: 

262 text = "No (0)" 

263 elif value == NA: 

264 text = "N/A" 

265 else: 

266 text = None 

267 return answer(text) 

268 

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

270 d = self.get_score_dict() 

271 h = f""" 

272 <div class="{CssClass.SUMMARY}"> 

273 <table class="{CssClass.SUMMARY}"> 

274 {self.get_is_complete_tr(req)} 

275 <tr> 

276 <td>Total</td> 

277 <td>{self.report_score(d['total'])}</td> 

278 </tr> 

279 <tr> 

280 <td>Activity: hygiene</td> 

281 <td>{self.report_score(d['hygiene'])}</td> 

282 </tr> 

283 <tr> 

284 <td>Activity: dressing</td> 

285 <td>{self.report_score(d['dressing'])}</td> 

286 </tr> 

287 <tr> 

288 <td>Activity: continence</td> 

289 <td>{self.report_score(d['continence'])}</td> 

290 </tr> 

291 <tr> 

292 <td>Activity: eating</td> 

293 <td>{self.report_score(d['eating'])}</td> 

294 </tr> 

295 <tr> 

296 <td>Basic activities of daily living (BADLs) (hygiene, 

297 dressing, continence, eating)</td> 

298 <td>{self.report_score(d['badl'])}</td> 

299 </tr> 

300 <tr> 

301 <td>Activity: meal preparation</td> 

302 <td>{self.report_score(d['mealprep'])}</td> 

303 </tr> 

304 <tr> 

305 <td>Activity: telephone</td> 

306 <td>{self.report_score(d['telephone'])}</td> 

307 </tr> 

308 <tr> 

309 <td>Activity: outings</td> 

310 <td>{self.report_score(d['outing'])}</td> 

311 </tr> 

312 <tr> 

313 <td>Activity: finance</td> 

314 <td>{self.report_score(d['finance'])}</td> 

315 </tr> 

316 <tr> 

317 <td>Activity: medications</td> 

318 <td>{self.report_score(d['medications'])}</td> 

319 </tr> 

320 <tr> 

321 <td>Activity: leisure</td> 

322 <td>{self.report_score(d['leisure'])}</td> 

323 </tr> 

324 <tr> 

325 <td>Instrumental activities of daily living (IADLs) 

326 (meal prep., telephone, outings, finance, medications, 

327 leisure)</td> 

328 <td>{self.report_score(d['iadl'])}</td> 

329 </tr> 

330 <tr> 

331 <td>Phase: initiation</td> 

332 <td>{self.report_score(d['initiation'])}</td> 

333 </tr> 

334 <tr> 

335 <td>Phase: planning/organisation</td> 

336 <td>{self.report_score(d['planning'])}</td> 

337 </tr> 

338 <tr> 

339 <td>Phase: execution/performance</td> 

340 <td>{self.report_score(d['execution'])}</td> 

341 </tr> 

342 </table> 

343 </div> 

344 <table class="{CssClass.TASKDETAIL}"> 

345 <tr> 

346 <th width="50%">Question (I = initiation, P = planning, 

347 E = execution)</th> 

348 <th width="50%">Answer</th> 

349 </tr> 

350 """ 

351 for group in self.GROUPS: 

352 h += subheading_spanning_two_columns(self.wxstring(req, group)) 

353 for item in self.ITEMS: 

354 if not item.startswith(group): 

355 continue 

356 q = self.wxstring(req, item) 

357 if "_init_" in item: 

358 q += " (I)" 

359 elif "_plan_" in item: 

360 q += " (P)" 

361 elif "_exec_" in item: 

362 q += " (E)" 

363 else: 

364 # Shouldn't happen 

365 q += " (?)" 

366 h += tr(q, self.report_answer(item)) 

367 h += f""" 

368 </table> 

369 {DATA_COLLECTION_UNLESS_UPGRADED_DIV} 

370 """ 

371 return h