Coverage for tasks/mds_updrs.py: 86%

1"""

2 camcops_server/tasks/mds_updrs.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 List, Optional

29

30 from sqlalchemy.orm import Mapped

31

32 from camcops_server.cc_modules.cc_cache import cache_region_static, fkg

33 from camcops_server.cc_modules.cc_constants import (

34 CssClass,

35 DATA_COLLECTION_ONLY_DIV,

36)

37 from camcops_server.cc_modules.cc_html import tr_qa

38 from camcops_server.cc_modules.cc_request import CamcopsRequest

39 from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup

40 from camcops_server.cc_modules.cc_sqla_coltypes import (

41 BIT_CHECKER,

42 gen_camcops_columns,

43 get_camcops_column_attr_names,

44 mapped_camcops_column,

45 ZERO_TO_TWO_CHECKER,

46 ZERO_TO_FOUR_CHECKER,

47 ZERO_TO_FIVE_CHECKER,

48)

49 from camcops_server.cc_modules.cc_task import (

50 Task,

51 TaskHasPatientMixin,

52 TaskHasClinicianMixin,

53)

54

55

56# =============================================================================

57# MDS-UPDRS (crippled)

58# =============================================================================

59

60

61 class MdsUpdrs(TaskHasClinicianMixin, TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501

62 """

63 Server implementation of the MDS-UPDRS task.

64

65 Has clinician as of v2.0.0.

66 """

67

68 __tablename__ = "mds_updrs"

69 shortname = "MDS-UPDRS"

70

71 main_cmt = " (0 normal, 1 slight, 2 mild, 3 moderate, 4 severe)"

72 main_pv = ZERO_TO_FOUR_CHECKER

73 informant_cmt = " (0 patient, 1 caregiver, 2 both)"

74 informant_pv = ZERO_TO_TWO_CHECKER

75 yn_cmt = " (0 no, 1 yes)"

76 on_off_cmt = " (0 off, 1 on)"

77 hy_pv = ZERO_TO_FIVE_CHECKER

78

79 # Part I

80 q1a: Mapped[Optional[int]] = mapped_camcops_column(

81 permitted_value_checker=informant_pv,

82 comment="Part I: informant for Q1.1-1.6" + informant_cmt,

83 )

84 q1_1: Mapped[Optional[int]] = mapped_camcops_column(

85 permitted_value_checker=main_pv,

86 comment="Part I, Q1.1 " + main_cmt,

87 )

88 q1_2: Mapped[Optional[int]] = mapped_camcops_column(

89 permitted_value_checker=main_pv,

90 comment="Part I, Q1.2 " + main_cmt,

91 )

92 q1_3: Mapped[Optional[int]] = mapped_camcops_column(

93 permitted_value_checker=main_pv,

94 comment="Part I, Q1.3 " + main_cmt,

95 )

96 q1_4: Mapped[Optional[int]] = mapped_camcops_column(

97 permitted_value_checker=main_pv,

98 comment="Part I, Q1.4 " + main_cmt,

99 )

100 q1_5: Mapped[Optional[int]] = mapped_camcops_column(

101 permitted_value_checker=main_pv,

102 comment="Part I, Q1.5 " + main_cmt,

103 )

104 q1_6: Mapped[Optional[int]] = mapped_camcops_column(

105 permitted_value_checker=main_pv,

106 comment="Part I, Q1.6 " + main_cmt,

107 )

108 q1_6a: Mapped[Optional[int]] = mapped_camcops_column(

109 permitted_value_checker=informant_pv,

110 comment="Part I, Q1.6a: informant for Q1.7-1.13" + informant_cmt,

111 )

112 q1_7: Mapped[Optional[int]] = mapped_camcops_column(

113 permitted_value_checker=main_pv,

114 comment="Part I, Q1.7 " + main_cmt,

115 )

116 q1_8: Mapped[Optional[int]] = mapped_camcops_column(

117 permitted_value_checker=main_pv,

118 comment="Part I, Q1.8 " + main_cmt,

119 )

120 q1_9: Mapped[Optional[int]] = mapped_camcops_column(

121 permitted_value_checker=main_pv,

122 comment="Part I, Q1.9 " + main_cmt,

123 )

124 q1_10: Mapped[Optional[int]] = mapped_camcops_column(

125 permitted_value_checker=main_pv,

126 comment="Part I, Q1.10 " + main_cmt,

127 )

128 q1_11: Mapped[Optional[int]] = mapped_camcops_column(

129 permitted_value_checker=main_pv,

130 comment="Part I, Q1.11 " + main_cmt,

131 )

132 q1_12: Mapped[Optional[int]] = mapped_camcops_column(

133 permitted_value_checker=main_pv,

134 comment="Part I, Q1.12 " + main_cmt,

135 )

136 q1_13: Mapped[Optional[int]] = mapped_camcops_column(

137 permitted_value_checker=main_pv,

138 comment="Part I, Q1.13 " + main_cmt,

139 )

140

141 # Part II

142 q2_1: Mapped[Optional[int]] = mapped_camcops_column(

143 permitted_value_checker=main_pv,

144 comment="Part II, Q2.1 " + main_cmt,

145 )

146 q2_2: Mapped[Optional[int]] = mapped_camcops_column(

147 permitted_value_checker=main_pv,

148 comment="Part II, Q2.2 " + main_cmt,

149 )

150 q2_3: Mapped[Optional[int]] = mapped_camcops_column(

151 permitted_value_checker=main_pv,

152 comment="Part II, Q2.3 " + main_cmt,

153 )

154 q2_4: Mapped[Optional[int]] = mapped_camcops_column(

155 permitted_value_checker=main_pv,

156 comment="Part II, Q2.4 " + main_cmt,

157 )

158 q2_5: Mapped[Optional[int]] = mapped_camcops_column(

159 permitted_value_checker=main_pv,

160 comment="Part II, Q2.5 " + main_cmt,

161 )

162 q2_6: Mapped[Optional[int]] = mapped_camcops_column(

163 permitted_value_checker=main_pv,

164 comment="Part II, Q2.6 " + main_cmt,

165 )

166 q2_7: Mapped[Optional[int]] = mapped_camcops_column(

167 permitted_value_checker=main_pv,

168 comment="Part II, Q2.7 " + main_cmt,

169 )

170 q2_8: Mapped[Optional[int]] = mapped_camcops_column(

171 permitted_value_checker=main_pv,

172 comment="Part II, Q2.8 " + main_cmt,

173 )

174 q2_9: Mapped[Optional[int]] = mapped_camcops_column(

175 permitted_value_checker=main_pv,

176 comment="Part II, Q2.9 " + main_cmt,

177 )

178 q2_10: Mapped[Optional[int]] = mapped_camcops_column(

179 permitted_value_checker=main_pv,

180 comment="Part II, Q2.10 " + main_cmt,

181 )

182 q2_11: Mapped[Optional[int]] = mapped_camcops_column(

183 permitted_value_checker=main_pv,

184 comment="Part II, Q2.11 " + main_cmt,

185 )

186 q2_12: Mapped[Optional[int]] = mapped_camcops_column(

187 permitted_value_checker=main_pv,

188 comment="Part II, Q2.12 " + main_cmt,

189 )

190 q2_13: Mapped[Optional[int]] = mapped_camcops_column(

191 permitted_value_checker=main_pv,

192 comment="Part II, Q2.13 " + main_cmt,

193 )

194

195 # Part III

196 q3a: Mapped[Optional[bool]] = mapped_camcops_column(

197 permitted_value_checker=BIT_CHECKER,

198 comment="Part III, Q3a (medication) " + yn_cmt,

199 )

200 q3b: Mapped[Optional[bool]] = mapped_camcops_column(

201 permitted_value_checker=BIT_CHECKER,

202 comment="Part III, Q3b (clinical state) " + on_off_cmt,

203 )

204 q3c: Mapped[Optional[bool]] = mapped_camcops_column(

205 permitted_value_checker=BIT_CHECKER,

206 comment="Part III, Q3c (levodopa) " + yn_cmt,

207 )

208 q3c1: Mapped[Optional[float]] = mapped_camcops_column(

209 comment="Part III, Q3c.1 (minutes since last dose)"

210 )

211 q3_1: Mapped[Optional[int]] = mapped_camcops_column(

212 permitted_value_checker=main_pv,

213 comment="Part III, Q3.1 " + main_cmt,

214 )

215 q3_2: Mapped[Optional[int]] = mapped_camcops_column(

216 permitted_value_checker=main_pv,

217 comment="Part III, Q3.2 " + main_cmt,

218 )

219 q3_3a: Mapped[Optional[int]] = mapped_camcops_column(

220 permitted_value_checker=main_pv,

221 comment="Part III, Q3.3a " + main_cmt,

222 )

223 q3_3b: Mapped[Optional[int]] = mapped_camcops_column(

224 permitted_value_checker=main_pv,

225 comment="Part III, Q3.3b " + main_cmt,

226 )

227 q3_3c: Mapped[Optional[int]] = mapped_camcops_column(

228 permitted_value_checker=main_pv,

229 comment="Part III, Q3.3c " + main_cmt,

230 )

231 q3_3d: Mapped[Optional[int]] = mapped_camcops_column(

232 permitted_value_checker=main_pv,

233 comment="Part III, Q3.3d " + main_cmt,

234 )

235 q3_3e: Mapped[Optional[int]] = mapped_camcops_column(

236 permitted_value_checker=main_pv,

237 comment="Part III, Q3.3e " + main_cmt,

238 )

239 q3_4a: Mapped[Optional[int]] = mapped_camcops_column(

240 permitted_value_checker=main_pv,

241 comment="Part III, Q3.4a " + main_cmt,

242 )

243 q3_4b: Mapped[Optional[int]] = mapped_camcops_column(

244 permitted_value_checker=main_pv,

245 comment="Part III, Q3.4b " + main_cmt,

246 )

247 q3_5a: Mapped[Optional[int]] = mapped_camcops_column(

248 permitted_value_checker=main_pv,

249 comment="Part III, Q3.5a " + main_cmt,

250 )

251 q3_5b: Mapped[Optional[int]] = mapped_camcops_column(

252 permitted_value_checker=main_pv,

253 comment="Part III, Q3.5b " + main_cmt,

254 )

255 q3_6a: Mapped[Optional[int]] = mapped_camcops_column(

256 permitted_value_checker=main_pv,

257 comment="Part III, Q3.6a " + main_cmt,

258 )

259 q3_6b: Mapped[Optional[int]] = mapped_camcops_column(

260 permitted_value_checker=main_pv,

261 comment="Part III, Q3.6b " + main_cmt,

262 )

263 q3_7a: Mapped[Optional[int]] = mapped_camcops_column(

264 permitted_value_checker=main_pv,

265 comment="Part III, Q3.7a " + main_cmt,

266 )

267 q3_7b: Mapped[Optional[int]] = mapped_camcops_column(

268 permitted_value_checker=main_pv,

269 comment="Part III, Q3.7b " + main_cmt,

270 )

271 q3_8a: Mapped[Optional[int]] = mapped_camcops_column(

272 permitted_value_checker=main_pv,

273 comment="Part III, Q3.8a " + main_cmt,

274 )

275 q3_8b: Mapped[Optional[int]] = mapped_camcops_column(

276 permitted_value_checker=main_pv,

277 comment="Part III, Q3.8b " + main_cmt,

278 )

279 q3_9: Mapped[Optional[int]] = mapped_camcops_column(

280 permitted_value_checker=main_pv,

281 comment="Part III, Q3.9 " + main_cmt,

282 )

283 q3_10: Mapped[Optional[int]] = mapped_camcops_column(

284 permitted_value_checker=main_pv,

285 comment="Part III, Q3.10 " + main_cmt,

286 )

287 q3_11: Mapped[Optional[int]] = mapped_camcops_column(

288 permitted_value_checker=main_pv,

289 comment="Part III, Q3.11 " + main_cmt,

290 )

291 q3_12: Mapped[Optional[int]] = mapped_camcops_column(

292 permitted_value_checker=main_pv,

293 comment="Part III, Q3.12 " + main_cmt,

294 )

295 q3_13: Mapped[Optional[int]] = mapped_camcops_column(

296 permitted_value_checker=main_pv,

297 comment="Part III, Q3.13 " + main_cmt,

298 )

299 q3_14: Mapped[Optional[int]] = mapped_camcops_column(

300 permitted_value_checker=main_pv,

301 comment="Part III, Q3.14 " + main_cmt,

302 )

303 q3_15a: Mapped[Optional[int]] = mapped_camcops_column(

304 permitted_value_checker=main_pv,

305 comment="Part III, Q3.15a " + main_cmt,

306 )

307 q3_15b: Mapped[Optional[int]] = mapped_camcops_column(

308 permitted_value_checker=main_pv,

309 comment="Part III, Q3.15b " + main_cmt,

310 )

311 q3_16a: Mapped[Optional[int]] = mapped_camcops_column(

312 permitted_value_checker=main_pv,

313 comment="Part III, Q3.16a " + main_cmt,

314 )

315 q3_16b: Mapped[Optional[int]] = mapped_camcops_column(

316 permitted_value_checker=main_pv,

317 comment="Part III, Q3.16b " + main_cmt,

318 )

319 q3_17a: Mapped[Optional[int]] = mapped_camcops_column(

320 permitted_value_checker=main_pv,

321 comment="Part III, Q3.17a " + main_cmt,

322 )

323 q3_17b: Mapped[Optional[int]] = mapped_camcops_column(

324 permitted_value_checker=main_pv,

325 comment="Part III, Q3.17b " + main_cmt,

326 )

327 q3_17c: Mapped[Optional[int]] = mapped_camcops_column(

328 permitted_value_checker=main_pv,

329 comment="Part III, Q3.17c " + main_cmt,

330 )

331 q3_17d: Mapped[Optional[int]] = mapped_camcops_column(

332 permitted_value_checker=main_pv,

333 comment="Part III, Q3.17d " + main_cmt,

334 )

335 q3_17e: Mapped[Optional[int]] = mapped_camcops_column(

336 permitted_value_checker=main_pv,

337 comment="Part III, Q3.17e " + main_cmt,

338 )

339 q3_18: Mapped[Optional[int]] = mapped_camcops_column(

340 permitted_value_checker=main_pv,

341 comment="Part III, Q3.18 " + main_cmt,

342 )

343 q3_dyskinesia_present: Mapped[Optional[bool]] = mapped_camcops_column(

344 permitted_value_checker=BIT_CHECKER,

345 comment="Part III, q3_dyskinesia_present " + yn_cmt,

346 )

347 q3_dyskinesia_interfered: Mapped[Optional[bool]] = mapped_camcops_column(

348 permitted_value_checker=BIT_CHECKER,

349 comment="Part III, q3_dyskinesia_interfered " + yn_cmt,

350 )

351 q3_hy_stage: Mapped[Optional[int]] = mapped_camcops_column(

352 permitted_value_checker=hy_pv,

353 comment="Part III, q3_hy_stage (0-5)",

354 )

355

356 # Part IV

357 q4_1: Mapped[Optional[int]] = mapped_camcops_column(

358 permitted_value_checker=main_pv,

359 comment="Part IV, Q4.1 " + main_cmt,

360 )

361 q4_2: Mapped[Optional[int]] = mapped_camcops_column(

362 permitted_value_checker=main_pv,

363 comment="Part IV, Q4.2 " + main_cmt,

364 )

365 q4_3: Mapped[Optional[int]] = mapped_camcops_column(

366 permitted_value_checker=main_pv,

367 comment="Part IV, Q4.3 " + main_cmt,

368 )

369 q4_4: Mapped[Optional[int]] = mapped_camcops_column(

370 permitted_value_checker=main_pv,

371 comment="Part IV, Q4.4 " + main_cmt,

372 )

373 q4_5: Mapped[Optional[int]] = mapped_camcops_column(

374 permitted_value_checker=main_pv,

375 comment="Part IV, Q4.5 " + main_cmt,

376 )

377 q4_6: Mapped[Optional[int]] = mapped_camcops_column(

378 permitted_value_checker=main_pv,

379 comment="Part IV, Q4.6 " + main_cmt,

380 )

381

382 @staticmethod

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

384 _ = req.gettext

385 return _(

386 "Movement Disorder Society-Sponsored Revision of the Unified "

387 "Parkinson’s Disease Rating Scale (data collection only)"

388 )

389

390 @classmethod

391 @cache_region_static.cache_on_arguments(function_key_generator=fkg)

392 def task_fields_except_3c1(cls) -> List[str]:

393 task_fields = get_camcops_column_attr_names(cls)

394 return [x for x in task_fields if x != "q3c1"]

395

396 def is_complete(self) -> bool:

397 return (

398 self.field_contents_valid()

399 and self.all_fields_not_none(self.task_fields_except_3c1())

400 and (self.q3c1 is not None or not self.q3c)

401 )

402

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

404 q_a = ""

405 for attrname, column in gen_camcops_columns(self):

406 if attrname.startswith("clinician_"): # not the most elegant!

407 continue

408 question = column.comment

409 value = getattr(self, attrname)

410 q_a += tr_qa(question, value)

411 return f"""

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

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

414 {self.get_is_complete_tr(req)}

415 </table>

416 </div>

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

418 <tr>

419 <th width="70%">Question</th>

420 <th width="30%">Answer</th>

421 </tr>

422 {q_a}

423 </table>

424 {DATA_COLLECTION_ONLY_DIV}

425 """

426

427 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:

428 if not self.is_complete():

429 return []

430 return [SnomedExpression(req.snomed(SnomedLookup.UPDRS_SCALE))]