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
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/mds_updrs.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28from typing import List, Optional
30from sqlalchemy.orm import Mapped
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)
56# =============================================================================
57# MDS-UPDRS (crippled)
58# =============================================================================
61class MdsUpdrs(TaskHasClinicianMixin, TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501
62 """
63 Server implementation of the MDS-UPDRS task.
65 Has clinician as of v2.0.0.
66 """
68 __tablename__ = "mds_updrs"
69 shortname = "MDS-UPDRS"
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
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 )
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 )
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 )
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 )
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 )
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"]
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 )
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 """
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))]