Coverage for tasks/cope.py: 60%
84 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/cope.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"""
30from typing import Any, Dict, List, Tuple, Type
32from sqlalchemy.ext.declarative import DeclarativeMeta
33from sqlalchemy.sql.schema import Column
34from sqlalchemy.sql.sqltypes import Integer, UnicodeText
36from camcops_server.cc_modules.cc_constants import CssClass
37from camcops_server.cc_modules.cc_db import add_multiple_columns
38from camcops_server.cc_modules.cc_html import tr_qa
39from camcops_server.cc_modules.cc_request import CamcopsRequest
40from camcops_server.cc_modules.cc_sqla_coltypes import (
41 CamcopsColumn,
42 BIT_CHECKER,
43 PermittedValueChecker,
44)
45from camcops_server.cc_modules.cc_summaryelement import SummaryElement
46from camcops_server.cc_modules.cc_task import (
47 get_from_dict,
48 Task,
49 TaskHasPatientMixin,
50)
53# =============================================================================
54# COPE_Brief
55# =============================================================================
58class CopeBriefMetaclass(DeclarativeMeta):
59 # noinspection PyInitNewSignature
60 def __init__(
61 cls: Type["CopeBrief"],
62 name: str,
63 bases: Tuple[Type, ...],
64 classdict: Dict[str, Any],
65 ) -> None:
66 add_multiple_columns(
67 cls,
68 "q",
69 1,
70 cls.NQUESTIONS,
71 minimum=0,
72 maximum=3,
73 comment_fmt="Q{n}, {s} (0 not at all - 3 a lot)",
74 comment_strings=[
75 "work/activities to take mind off", # 1
76 "concentrating efforts on doing something about it",
77 "saying it's unreal",
78 "alcohol/drugs to feel better",
79 "emotional support from others", # 5
80 "given up trying to deal with it",
81 "taking action to make situation better",
82 "refusing to believe it's happened",
83 "saying things to let unpleasant feelings escape",
84 "getting help/advice from others", # 10
85 "alcohol/drugs to get through it",
86 "trying to see it in a more positive light",
87 "criticizing myself",
88 "trying to come up with a strategy",
89 "getting comfort/understanding from someone", # 15
90 "giving up the attempt to cope",
91 "looking for something good in what's happening",
92 "making jokes about it",
93 "doing something to think about it less",
94 "accepting reality of the fact it's happened", # 20
95 "expressing negative feelings",
96 "seeking comfort in religion/spirituality",
97 "trying to get help/advice from others about what to do",
98 "learning to live with it",
99 "thinking hard about what steps to take", # 25
100 "blaming myself",
101 "praying/meditating",
102 "making fun of the situation", # 28
103 ],
104 )
105 super().__init__(name, bases, classdict)
108class CopeBrief(TaskHasPatientMixin, Task, metaclass=CopeBriefMetaclass):
109 """
110 Server implementation of the COPE-Brief task.
111 """
113 __tablename__ = "cope_brief"
114 shortname = "COPE-Brief"
115 extrastring_taskname = "cope"
116 info_filename_stem = "cope"
118 NQUESTIONS = 28
119 RELATIONSHIP_OTHER_CODE = 0
120 RELATIONSHIPS_FIRST = 0
121 RELATIONSHIPS_FIRST_NON_OTHER = 1
122 RELATIONSHIPS_LAST = 9
124 completed_by_patient = CamcopsColumn(
125 "completed_by_patient",
126 Integer,
127 permitted_value_checker=BIT_CHECKER,
128 comment="Task completed by patient? (0 no, 1 yes)",
129 )
130 completed_by = Column(
131 "completed_by",
132 UnicodeText,
133 comment="Name of person task completed by (if not by patient)",
134 )
135 relationship_to_patient = CamcopsColumn(
136 "relationship_to_patient",
137 Integer,
138 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=9),
139 comment="Relationship of responder to patient (0 other, 1 wife, "
140 "2 husband, 3 daughter, 4 son, 5 sister, 6 brother, "
141 "7 mother, 8 father, 9 friend)",
142 )
143 relationship_to_patient_other = Column(
144 "relationship_to_patient_other",
145 UnicodeText,
146 comment="Relationship of responder to patient (if OTHER chosen)",
147 )
149 @staticmethod
150 def longname(req: "CamcopsRequest") -> str:
151 _ = req.gettext
152 return _("Brief COPE Inventory")
154 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
155 return self.standard_task_summary_fields() + [
156 SummaryElement(
157 name="self_distraction",
158 coltype=Integer(),
159 value=self.self_distraction(),
160 comment="Self-distraction (2-8)",
161 ),
162 SummaryElement(
163 name="active_coping",
164 coltype=Integer(),
165 value=self.active_coping(),
166 comment="Active coping (2-8)",
167 ),
168 SummaryElement(
169 name="denial",
170 coltype=Integer(),
171 value=self.denial(),
172 comment="Denial (2-8)",
173 ),
174 SummaryElement(
175 name="substance_use",
176 coltype=Integer(),
177 value=self.substance_use(),
178 comment="Substance use (2-8)",
179 ),
180 SummaryElement(
181 name="emotional_support",
182 coltype=Integer(),
183 value=self.emotional_support(),
184 comment="Use of emotional support (2-8)",
185 ),
186 SummaryElement(
187 name="instrumental_support",
188 coltype=Integer(),
189 value=self.instrumental_support(),
190 comment="Use of instrumental support (2-8)",
191 ),
192 SummaryElement(
193 name="behavioural_disengagement",
194 coltype=Integer(),
195 value=self.behavioural_disengagement(),
196 comment="Behavioural disengagement (2-8)",
197 ),
198 SummaryElement(
199 name="venting",
200 coltype=Integer(),
201 value=self.venting(),
202 comment="Venting (2-8)",
203 ),
204 SummaryElement(
205 name="positive_reframing",
206 coltype=Integer(),
207 value=self.positive_reframing(),
208 comment="Positive reframing (2-8)",
209 ),
210 SummaryElement(
211 name="planning",
212 coltype=Integer(),
213 value=self.planning(),
214 comment="Planning (2-8)",
215 ),
216 SummaryElement(
217 name="humour",
218 coltype=Integer(),
219 value=self.humour(),
220 comment="Humour (2-8)",
221 ),
222 SummaryElement(
223 name="acceptance",
224 coltype=Integer(),
225 value=self.acceptance(),
226 comment="Acceptance (2-8)",
227 ),
228 SummaryElement(
229 name="religion",
230 coltype=Integer(),
231 value=self.religion(),
232 comment="Religion (2-8)",
233 ),
234 SummaryElement(
235 name="self_blame",
236 coltype=Integer(),
237 value=self.self_blame(),
238 comment="Self-blame (2-8)",
239 ),
240 ]
242 def is_complete_responder(self) -> bool:
243 if self.completed_by_patient is None:
244 return False
245 if self.completed_by_patient:
246 return True
247 if not self.completed_by or self.relationship_to_patient is None:
248 return False
249 if (
250 self.relationship_to_patient == self.RELATIONSHIP_OTHER_CODE
251 and not self.relationship_to_patient_other
252 ):
253 return False
254 return True
256 def is_complete(self) -> bool:
257 return (
258 self.is_complete_responder()
259 and self.all_fields_not_none(
260 [f"q{n}" for n in range(1, self.NQUESTIONS + 1)]
261 )
262 and self.field_contents_valid()
263 )
265 def self_distraction(self) -> int:
266 return self.sum_fields(["q1", "q19"])
268 def active_coping(self) -> int:
269 return self.sum_fields(["q2", "q7"])
271 def denial(self) -> int:
272 return self.sum_fields(["q3", "q8"])
274 def substance_use(self) -> int:
275 return self.sum_fields(["q4", "q11"])
277 def emotional_support(self) -> int:
278 return self.sum_fields(["q5", "q15"])
280 def instrumental_support(self) -> int:
281 return self.sum_fields(["q10", "q23"])
283 def behavioural_disengagement(self) -> int:
284 return self.sum_fields(["q6", "q16"])
286 def venting(self) -> int:
287 return self.sum_fields(["q9", "q21"])
289 def positive_reframing(self) -> int:
290 return self.sum_fields(["q12", "q17"])
292 def planning(self) -> int:
293 return self.sum_fields(["q14", "q25"])
295 def humour(self) -> int:
296 return self.sum_fields(["q18", "q28"])
298 def acceptance(self) -> int:
299 return self.sum_fields(["q20", "q24"])
301 def religion(self) -> int:
302 return self.sum_fields(["q22", "q27"])
304 def self_blame(self) -> int:
305 return self.sum_fields(["q13", "q26"])
307 def get_task_html(self, req: CamcopsRequest) -> str:
308 answer_dict = {None: None}
309 for option in range(0, 3 + 1):
310 answer_dict[option] = (
311 str(option) + " — " + self.wxstring(req, "a" + str(option))
312 )
313 q_a = ""
314 for q in range(1, self.NQUESTIONS + 1):
315 q_a += tr_qa(
316 f"Q{q}. {self.wxstring(req, 'q' + str(q))}",
317 get_from_dict(answer_dict, getattr(self, "q" + str(q))),
318 )
319 return f"""
320 <div class="{CssClass.SUMMARY}">
321 <table class="{CssClass.SUMMARY}">
322 {self.get_is_complete_tr(req)}
323 {tr_qa("Self-distraction (Q1, Q19)",
324 self.self_distraction())}
325 {tr_qa("Active coping (Q2, Q7)", self.active_coping())}
326 {tr_qa("Denial (Q3, Q8)", self.denial())}
327 {tr_qa("Substance use (Q4, Q11)", self.substance_use())}
328 {tr_qa("Use of emotional support (Q5, Q15)",
329 self.emotional_support())}
330 {tr_qa("Use of instrumental support (Q10, Q23)",
331 self.instrumental_support())}
332 {tr_qa("Behavioural disengagement (Q6, Q16)",
333 self.behavioural_disengagement())}
334 {tr_qa("Venting (Q9, Q21)", self.venting())}
335 {tr_qa("Positive reframing (Q12, Q17)",
336 self.positive_reframing())}
337 {tr_qa("Planning (Q14, Q25)", self.planning())}
338 {tr_qa("Humour (Q18, Q28)", self.humour())}
339 {tr_qa("Acceptance (Q20, Q24)", self.acceptance())}
340 {tr_qa("Religion (Q22, Q27)", self.religion())}
341 {tr_qa("Self-blame (Q13, Q26)", self.self_blame())}
342 </table>
343 </div>
344 <div class="{CssClass.EXPLANATION}">
345 Individual items are scored 0–3 (as in Carver 1997 PMID
346 16250744), not 1–4 (as in
347 http://www.psy.miami.edu/faculty/ccarver/sclBrCOPE.html).
348 Summaries, which are all
349 based on two items, are therefore scored 0–6.
350 </div>
351 <table class="{CssClass.TASKDETAIL}">
352 <tr>
353 <th width="50%">Question</th>
354 <th width="50%">Answer</th>
355 </tr>
356 {q_a}
357 </table>
358 """