Coverage for tasks/cia.py: 52%
62 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/cia.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28**The Clinical Impairment Assessment questionnaire (CIA) task.**
30"""
32from typing import Any, Dict, Optional, Type, Tuple
34from cardinal_pythonlib.stringfunc import strnumlist, strseq
35from sqlalchemy.ext.declarative import DeclarativeMeta
36from sqlalchemy.sql.sqltypes import Integer
38from camcops_server.cc_modules.cc_constants import CssClass
39from camcops_server.cc_modules.cc_db import add_multiple_columns
40from camcops_server.cc_modules.cc_html import tr_qa, tr, answer
41from camcops_server.cc_modules.cc_request import CamcopsRequest
42from camcops_server.cc_modules.cc_task import TaskHasPatientMixin, Task
43from camcops_server.cc_modules.cc_text import SS
46class CiaMetaclass(DeclarativeMeta):
47 def __init__(
48 cls: Type["Cia"],
49 name: str,
50 bases: Tuple[Type, ...],
51 classdict: Dict[str, Any],
52 ) -> None:
54 add_multiple_columns(
55 cls,
56 cls.Q_PREFIX,
57 cls.FIRST_Q,
58 cls.LAST_Q,
59 coltype=Integer,
60 minimum=0,
61 maximum=3,
62 comment_fmt=cls.Q_PREFIX + "{n} - {s}",
63 comment_strings=[
64 "difficult to concentrate",
65 "critical of self",
66 "going out",
67 "affected work performance",
68 "forgetful",
69 "everyday decisions",
70 "meals with family",
71 "upset",
72 "ashamed",
73 "difficult to eat out",
74 "guilty",
75 "things used to enjoy",
76 "absent-minded",
77 "failure",
78 "relationships",
79 "worry",
80 ],
81 )
83 super().__init__(name, bases, classdict)
86class Cia(TaskHasPatientMixin, Task, metaclass=CiaMetaclass):
87 __tablename__ = "cia"
88 shortname = "CIA"
90 Q_PREFIX = "q"
91 FIRST_Q = 1
92 LAST_Q = 16
93 MAX_SCORE = 48
95 ALL_FIELD_NAMES = strseq(Q_PREFIX, FIRST_Q, LAST_Q)
96 MANDATORY_QUESTIONS = [1, 2, 5, 6, 8, 9, 11, 12, 13, 14, 15, 16]
97 MANDATORY_FIELD_NAMES = strnumlist(Q_PREFIX, MANDATORY_QUESTIONS)
99 @staticmethod
100 def longname(req: CamcopsRequest) -> str:
101 _ = req.gettext
102 return _("")
104 def is_complete(self) -> bool:
105 if self.any_fields_none(self.MANDATORY_FIELD_NAMES):
106 return False
108 return True
110 def global_score(self) -> Optional[float]:
111 """
112 The original paper states:
114 "To obtain the global CIA impairment score the ratings on all items are
115 added together with prorating of missing ratings, so long as at least
116 12 of the 16 items have been rated."
118 In our implementation all questions are mandatory except for 3, 4, 7
119 and 10. So there won't be fewer than 12 items rated for a complete
120 questionnaire.
121 """
122 if not self.is_complete():
123 return None
125 num_answered = self.n_fields_not_none(self.ALL_FIELD_NAMES)
126 scale_factor = self.LAST_Q / num_answered
128 return scale_factor * self.sum_fields(self.ALL_FIELD_NAMES)
130 def get_task_html(self, req: CamcopsRequest) -> str:
131 rows = ""
132 for q_num in range(self.FIRST_Q, self.LAST_Q + 1):
133 field = self.Q_PREFIX + str(q_num)
134 question_cell = "{}. {}".format(q_num, self.wxstring(req, field))
136 rows += tr_qa(question_cell, self.get_answer_cell(req, q_num))
138 global_score = self.global_score()
139 if global_score is None:
140 global_score_display = "?"
141 else:
142 global_score_display = "{:.2f} / {}".format(
143 global_score, self.MAX_SCORE
144 )
146 html = """
147 <div class="{CssClass.SUMMARY}">
148 <table class="{CssClass.SUMMARY}">
149 {tr_is_complete}
150 {global_score}
151 </table>
152 </div>
153 <table class="{CssClass.TASKDETAIL}">
154 <tr>
155 <th width="60%">Question</th>
156 <th width="40%">Score</th>
157 </tr>
158 {rows}
159 </table>
160 <div class="{CssClass.FOOTNOTES}">
161 [1] Sum for all questions with prorating of missing ratings,
162 so long as at least 12 of the 16 items have been rated.
163 </div>
164 """.format(
165 CssClass=CssClass,
166 tr_is_complete=self.get_is_complete_tr(req),
167 global_score=tr(
168 req.sstring(SS.TOTAL_SCORE) + "<sup>[1]</sup>",
169 answer(global_score_display),
170 ),
171 rows=rows,
172 )
173 return html
175 def get_answer_cell(self, req: CamcopsRequest, q_num: int) -> str:
176 q_field = self.Q_PREFIX + str(q_num)
178 score = getattr(self, q_field)
179 if score is None:
180 if q_num in self.MANDATORY_QUESTIONS:
181 return "?"
183 return req.sstring(SS.NA)
185 meaning = self.get_score_meaning(req, score)
186 return f"{score} [{meaning}]"
188 def get_score_meaning(self, req: CamcopsRequest, score: int) -> str:
189 return self.wxstring(req, f"option_{score}")