Coverage for tasks/mds_updrs.py: 86%

117 statements  

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

1""" 

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

28from typing import List, Optional 

29 

30from sqlalchemy.orm import Mapped 

31 

32from camcops_server.cc_modules.cc_cache import cache_region_static, fkg 

33from camcops_server.cc_modules.cc_constants import ( 

34 CssClass, 

35 DATA_COLLECTION_ONLY_DIV, 

36) 

37from camcops_server.cc_modules.cc_html import tr_qa 

38from camcops_server.cc_modules.cc_request import CamcopsRequest 

39from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

40from 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) 

49from camcops_server.cc_modules.cc_task import ( 

50 Task, 

51 TaskHasPatientMixin, 

52 TaskHasClinicianMixin, 

53) 

54 

55 

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

57# MDS-UPDRS (crippled) 

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

59 

60 

61class 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))]