Coverage for tasks/dad.py: 40%

1"""

2 camcops_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

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

29

30 from sqlalchemy.sql.sqltypes import Integer

31

32 from camcops_server.cc_modules.cc_constants import (

33 CssClass,

34 DATA_COLLECTION_UNLESS_UPGRADED_DIV,

35)

36 from camcops_server.cc_modules.cc_html import (

37 answer,

38 subheading_spanning_two_columns,

39 tr,

40)

41 from camcops_server.cc_modules.cc_request import CamcopsRequest

42 from camcops_server.cc_modules.cc_sqla_coltypes import (

43 camcops_column,

44 PermittedValueChecker,

45)

46 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

47 from camcops_server.cc_modules.cc_task import (

48 Task,

49 TaskHasClinicianMixin,

50 TaskHasPatientMixin,

51 TaskHasRespondentMixin,

52)

53

54 YES = 1

55 NO = 0

56 NA = -99

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

58

59

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

61# DAD

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

63

64

65 class 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