Hide keyboard shortcuts

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 

2 

3""" 

4camcops_server/tasks/dad.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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. 

16 

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. 

21 

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

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Any, Dict, Iterable, List, Tuple, Type 

30 

31from sqlalchemy.ext.declarative import DeclarativeMeta 

32from sqlalchemy.sql.sqltypes import Integer 

33 

34from camcops_server.cc_modules.cc_constants import ( 

35 CssClass, 

36 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

37) 

38from camcops_server.cc_modules.cc_html import ( 

39 answer, 

40 subheading_spanning_two_columns, 

41 tr, 

42) 

43from camcops_server.cc_modules.cc_request import CamcopsRequest 

44from camcops_server.cc_modules.cc_sqla_coltypes import ( 

45 CamcopsColumn, 

46 PermittedValueChecker, 

47) 

48from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

49from camcops_server.cc_modules.cc_task import ( 

50 Task, 

51 TaskHasClinicianMixin, 

52 TaskHasPatientMixin, 

53 TaskHasRespondentMixin, 

54) 

55 

56YES = 1 

57NO = 0 

58NA = -99 

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

60 

61 

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

63# DAD 

64# ============================================================================= 

65 

66class DadMetaclass(DeclarativeMeta): 

67 # noinspection PyInitNewSignature 

68 def __init__(cls: Type['Dad'], 

69 name: str, 

70 bases: Tuple[Type, ...], 

71 classdict: Dict[str, Any]) -> None: 

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

73 for colname in cls.ITEMS: 

74 setattr( 

75 cls, 

76 colname, 

77 CamcopsColumn(colname, Integer, 

78 permitted_value_checker=YN_NA_CHECKER, 

79 comment=colname + explan) 

80 ) 

81 super().__init__(name, bases, classdict) 

82 

83 

84class Dad(TaskHasPatientMixin, TaskHasRespondentMixin, TaskHasClinicianMixin, 

85 Task, 

86 metaclass=DadMetaclass): 

87 """ 

88 Server implementation of the DAD task. 

89 """ 

90 __tablename__ = "dad" 

91 shortname = "DAD" 

92 

93 GROUPS = [ 

94 "hygiene", 

95 "dressing", 

96 "continence", 

97 "eating", 

98 "mealprep", 

99 "telephone", 

100 "outing", 

101 "finance", 

102 "medications", 

103 "leisure" 

104 ] 

105 ITEMS = [ 

106 "hygiene_init_wash", 

107 "hygiene_init_teeth", 

108 "hygiene_init_hair", 

109 "hygiene_plan_wash", 

110 "hygiene_exec_wash", 

111 "hygiene_exec_hair", 

112 "hygiene_exec_teeth", 

113 

114 "dressing_init_dress", 

115 "dressing_plan_clothing", 

116 "dressing_plan_order", 

117 "dressing_exec_dress", 

118 "dressing_exec_undress", 

119 

120 "continence_init_toilet", 

121 "continence_exec_toilet", 

122 

123 "eating_init_eat", 

124 "eating_plan_utensils", 

125 "eating_exec_eat", 

126 

127 "mealprep_init_meal", 

128 "mealprep_plan_meal", 

129 "mealprep_exec_meal", 

130 

131 "telephone_init_phone", 

132 "telephone_plan_dial", 

133 "telephone_exec_conversation", 

134 "telephone_exec_message", 

135 

136 "outing_init_outing", 

137 "outing_plan_outing", 

138 "outing_exec_reach_destination", 

139 "outing_exec_mode_transportation", 

140 "outing_exec_return_with_shopping", 

141 

142 "finance_init_interest", 

143 "finance_plan_pay_bills", 

144 "finance_plan_organise_correspondence", 

145 "finance_exec_handle_money", 

146 

147 "medications_init_medication", 

148 "medications_exec_take_medications", 

149 

150 "leisure_init_interest_leisure", 

151 "leisure_init_interest_chores", 

152 "leisure_plan_chores", 

153 "leisure_exec_complete_chores", 

154 "leisure_exec_safe_at_home" 

155 ] 

156 

157 @staticmethod 

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

159 _ = req.gettext 

160 return _("Disability Assessment for Dementia") 

161 

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

163 d = self.get_score_dict() 

164 s = self.standard_task_summary_fields() 

165 for item in d: 

166 s.extend([ 

167 SummaryElement(name=item + "_n", 

168 coltype=Integer(), 

169 value=d[item][0], 

170 comment=item + " (numerator)"), 

171 SummaryElement(name=item + "_d", 

172 coltype=Integer(), 

173 value=d[item][1], 

174 comment=item + " (denominator)"), 

175 ]) 

176 return s 

177 

178 # noinspection PyMethodOverriding 

179 @staticmethod 

180 def is_complete() -> bool: 

181 return True 

182 

183 @classmethod 

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

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

186 

187 @classmethod 

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

189 return [item for item in cls.ITEMS 

190 if any(item.startswith(activity) for activity in activities)] 

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(self.get_items_activities( 

208 ['hygiene', 'dressing', 'continence', 'eating'])) 

209 mealprep = self.get_score(self.get_items_activity('mealprep')) 

210 telephone = self.get_score(self.get_items_activity('telephone')) 

211 outing = self.get_score(self.get_items_activity('outing')) 

212 finance = self.get_score(self.get_items_activity('finance')) 

213 medications = self.get_score(self.get_items_activity('medications')) 

214 leisure = self.get_score(self.get_items_activity('leisure')) 

215 iadl = self.get_score(self.get_items_activities( 

216 ['mealprep', 'telephone', 'outing', 'finance', 

217 'medications', 'leisure'])) 

218 initiation = self.get_score(self.get_items_phase('init')) 

219 planning = self.get_score(self.get_items_phase('plan')) 

220 execution = self.get_score(self.get_items_phase('exec')) 

221 # n for numerator, d for denominator 

222 return dict( 

223 total=total, 

224 hygiene=hygiene, 

225 dressing=dressing, 

226 continence=continence, 

227 eating=eating, 

228 badl=badl, 

229 mealprep=mealprep, 

230 telephone=telephone, 

231 outing=outing, 

232 finance=finance, 

233 medications=medications, 

234 leisure=leisure, 

235 iadl=iadl, 

236 initiation=initiation, 

237 planning=planning, 

238 execution=execution, 

239 ) 

240 

241 @staticmethod 

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

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

244 

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

246 value = getattr(self, field) 

247 if value == YES: 

248 text = "Yes (1)" 

249 elif value == NO: 

250 text = "No (0)" 

251 elif value == NA: 

252 text = "N/A" 

253 else: 

254 text = None 

255 return answer(text) 

256 

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

258 d = self.get_score_dict() 

259 h = f""" 

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

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

262 {self.get_is_complete_tr(req)} 

263 <tr> 

264 <td>Total</td> 

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

266 </tr> 

267 <tr> 

268 <td>Activity: hygiene</td> 

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

270 </tr> 

271 <tr> 

272 <td>Activity: dressing</td> 

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

274 </tr> 

275 <tr> 

276 <td>Activity: continence</td> 

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

278 </tr> 

279 <tr> 

280 <td>Activity: eating</td> 

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

282 </tr> 

283 <tr> 

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

285 dressing, continence, eating)</td> 

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

287 </tr> 

288 <tr> 

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

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

291 </tr> 

292 <tr> 

293 <td>Activity: telephone</td> 

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

295 </tr> 

296 <tr> 

297 <td>Activity: outings</td> 

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

299 </tr> 

300 <tr> 

301 <td>Activity: finance</td> 

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

303 </tr> 

304 <tr> 

305 <td>Activity: medications</td> 

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

307 </tr> 

308 <tr> 

309 <td>Activity: leisure</td> 

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

311 </tr> 

312 <tr> 

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

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

315 leisure)</td> 

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

317 </tr> 

318 <tr> 

319 <td>Phase: initiation</td> 

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

321 </tr> 

322 <tr> 

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

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

325 </tr> 

326 <tr> 

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

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

329 </tr> 

330 </table> 

331 </div> 

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

333 <tr> 

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

335 E = execution)</th> 

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

337 </tr> 

338 """ 

339 for group in self.GROUPS: 

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

341 for item in self.ITEMS: 

342 if not item.startswith(group): 

343 continue 

344 q = self.wxstring(req, item) 

345 if '_init_' in item: 

346 q += " (I)" 

347 elif '_plan_' in item: 

348 q += " (P)" 

349 elif '_exec_' in item: 

350 q += " (E)" 

351 else: 

352 # Shouldn't happen 

353 q += " (?)" 

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

355 h += f""" 

356 </table> 

357 {DATA_COLLECTION_UNLESS_UPGRADED_DIV} 

358 """ 

359 return h