Coverage for tasks/cope.py : 59%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4camcops_server/tasks/cope.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27"""
29from typing import Any, Dict, List, Tuple, Type
31from sqlalchemy.ext.declarative import DeclarativeMeta
32from sqlalchemy.sql.schema import Column
33from sqlalchemy.sql.sqltypes import Integer, UnicodeText
35from camcops_server.cc_modules.cc_constants import CssClass
36from camcops_server.cc_modules.cc_db import add_multiple_columns
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_sqla_coltypes import (
40 CamcopsColumn,
41 BIT_CHECKER,
42 PermittedValueChecker,
43)
44from camcops_server.cc_modules.cc_summaryelement import SummaryElement
45from camcops_server.cc_modules.cc_task import (
46 get_from_dict,
47 Task,
48 TaskHasPatientMixin,
49)
52# =============================================================================
53# COPE_Brief
54# =============================================================================
56class CopeBriefMetaclass(DeclarativeMeta):
57 # noinspection PyInitNewSignature
58 def __init__(cls: Type['CopeBrief'],
59 name: str,
60 bases: Tuple[Type, ...],
61 classdict: Dict[str, Any]) -> None:
62 add_multiple_columns(
63 cls, "q", 1, cls.NQUESTIONS,
64 minimum=0, maximum=3,
65 comment_fmt="Q{n}, {s} (0 not at all - 3 a lot)",
66 comment_strings=[
67 "work/activities to take mind off", # 1
68 "concentrating efforts on doing something about it",
69 "saying it's unreal",
70 "alcohol/drugs to feel better",
71 "emotional support from others", # 5
72 "given up trying to deal with it",
73 "taking action to make situation better",
74 "refusing to believe it's happened",
75 "saying things to let unpleasant feelings escape",
76 "getting help/advice from others", # 10
77 "alcohol/drugs to get through it",
78 "trying to see it in a more positive light",
79 "criticizing myself",
80 "trying to come up with a strategy",
81 "getting comfort/understanding from someone", # 15
82 "giving up the attempt to cope",
83 "looking for something good in what's happening",
84 "making jokes about it",
85 "doing something to think about it less",
86 "accepting reality of the fact it's happened", # 20
87 "expressing negative feelings",
88 "seeking comfort in religion/spirituality",
89 "trying to get help/advice from others about what to do",
90 "learning to live with it",
91 "thinking hard about what steps to take", # 25
92 "blaming myself",
93 "praying/meditating",
94 "making fun of the situation" # 28
95 ]
96 )
97 super().__init__(name, bases, classdict)
100class CopeBrief(TaskHasPatientMixin, Task,
101 metaclass=CopeBriefMetaclass):
102 """
103 Server implementation of the COPE-Brief task.
104 """
105 __tablename__ = "cope_brief"
106 shortname = "COPE-Brief"
107 extrastring_taskname = "cope"
109 NQUESTIONS = 28
110 RELATIONSHIP_OTHER_CODE = 0
111 RELATIONSHIPS_FIRST = 0
112 RELATIONSHIPS_FIRST_NON_OTHER = 1
113 RELATIONSHIPS_LAST = 9
115 completed_by_patient = CamcopsColumn(
116 "completed_by_patient", Integer,
117 permitted_value_checker=BIT_CHECKER,
118 comment="Task completed by patient? (0 no, 1 yes)"
119 )
120 completed_by = Column(
121 "completed_by", UnicodeText,
122 comment="Name of person task completed by (if not by patient)"
123 )
124 relationship_to_patient = CamcopsColumn(
125 "relationship_to_patient", Integer,
126 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=9),
127 comment="Relationship of responder to patient (0 other, 1 wife, "
128 "2 husband, 3 daughter, 4 son, 5 sister, 6 brother, "
129 "7 mother, 8 father, 9 friend)"
130 )
131 relationship_to_patient_other = Column(
132 "relationship_to_patient_other", UnicodeText,
133 comment="Relationship of responder to patient (if OTHER chosen)"
134 )
136 @staticmethod
137 def longname(req: "CamcopsRequest") -> str:
138 _ = req.gettext
139 return _("Brief COPE Inventory")
141 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
142 return self.standard_task_summary_fields() + [
143 SummaryElement(name="self_distraction",
144 coltype=Integer(),
145 value=self.self_distraction(),
146 comment="Self-distraction (2-8)"),
147 SummaryElement(name="active_coping",
148 coltype=Integer(),
149 value=self.active_coping(),
150 comment="Active coping (2-8)"),
151 SummaryElement(name="denial",
152 coltype=Integer(),
153 value=self.denial(),
154 comment="Denial (2-8)"),
155 SummaryElement(name="substance_use",
156 coltype=Integer(),
157 value=self.substance_use(),
158 comment="Substance use (2-8)"),
159 SummaryElement(name="emotional_support",
160 coltype=Integer(),
161 value=self.emotional_support(),
162 comment="Use of emotional support (2-8)"),
163 SummaryElement(name="instrumental_support",
164 coltype=Integer(),
165 value=self.instrumental_support(),
166 comment="Use of instrumental support (2-8)"),
167 SummaryElement(name="behavioural_disengagement",
168 coltype=Integer(),
169 value=self.behavioural_disengagement(),
170 comment="Behavioural disengagement (2-8)"),
171 SummaryElement(name="venting",
172 coltype=Integer(),
173 value=self.venting(),
174 comment="Venting (2-8)"),
175 SummaryElement(name="positive_reframing",
176 coltype=Integer(),
177 value=self.positive_reframing(),
178 comment="Positive reframing (2-8)"),
179 SummaryElement(name="planning",
180 coltype=Integer(),
181 value=self.planning(),
182 comment="Planning (2-8)"),
183 SummaryElement(name="humour",
184 coltype=Integer(),
185 value=self.humour(),
186 comment="Humour (2-8)"),
187 SummaryElement(name="acceptance",
188 coltype=Integer(),
189 value=self.acceptance(),
190 comment="Acceptance (2-8)"),
191 SummaryElement(name="religion",
192 coltype=Integer(),
193 value=self.religion(),
194 comment="Religion (2-8)"),
195 SummaryElement(name="self_blame",
196 coltype=Integer(),
197 value=self.self_blame(),
198 comment="Self-blame (2-8)"),
199 ]
201 def is_complete_responder(self) -> bool:
202 if self.completed_by_patient is None:
203 return False
204 if self.completed_by_patient:
205 return True
206 if not self.completed_by or self.relationship_to_patient is None:
207 return False
208 if (self.relationship_to_patient == self.RELATIONSHIP_OTHER_CODE and
209 not self.relationship_to_patient_other):
210 return False
211 return True
213 def is_complete(self) -> bool:
214 return (
215 self.is_complete_responder() and
216 self.all_fields_not_none(
217 [f"q{n}" for n in range(1, self.NQUESTIONS + 1)]) and
218 self.field_contents_valid()
219 )
221 def self_distraction(self) -> int:
222 return self.sum_fields(["q1", "q19"])
224 def active_coping(self) -> int:
225 return self.sum_fields(["q2", "q7"])
227 def denial(self) -> int:
228 return self.sum_fields(["q3", "q8"])
230 def substance_use(self) -> int:
231 return self.sum_fields(["q4", "q11"])
233 def emotional_support(self) -> int:
234 return self.sum_fields(["q5", "q15"])
236 def instrumental_support(self) -> int:
237 return self.sum_fields(["q10", "q23"])
239 def behavioural_disengagement(self) -> int:
240 return self.sum_fields(["q6", "q16"])
242 def venting(self) -> int:
243 return self.sum_fields(["q9", "q21"])
245 def positive_reframing(self) -> int:
246 return self.sum_fields(["q12", "q17"])
248 def planning(self) -> int:
249 return self.sum_fields(["q14", "q25"])
251 def humour(self) -> int:
252 return self.sum_fields(["q18", "q28"])
254 def acceptance(self) -> int:
255 return self.sum_fields(["q20", "q24"])
257 def religion(self) -> int:
258 return self.sum_fields(["q22", "q27"])
260 def self_blame(self) -> int:
261 return self.sum_fields(["q13", "q26"])
263 def get_task_html(self, req: CamcopsRequest) -> str:
264 answer_dict = {None: None}
265 for option in range(0, 3 + 1):
266 answer_dict[option] = (
267 str(option) + " — " + self.wxstring(req, "a" + str(option))
268 )
269 q_a = ""
270 for q in range(1, self.NQUESTIONS + 1):
271 q_a += tr_qa(
272 f"Q{q}. {self.wxstring(req, 'q' + str(q))}",
273 get_from_dict(answer_dict, getattr(self, "q" + str(q)))
274 )
275 return f"""
276 <div class="{CssClass.SUMMARY}">
277 <table class="{CssClass.SUMMARY}">
278 {self.get_is_complete_tr(req)}
279 {tr_qa("Self-distraction (Q1, Q19)",
280 self.self_distraction())}
281 {tr_qa("Active coping (Q2, Q7)", self.active_coping())}
282 {tr_qa("Denial (Q3, Q8)", self.denial())}
283 {tr_qa("Substance use (Q4, Q11)", self.substance_use())}
284 {tr_qa("Use of emotional support (Q5, Q15)",
285 self.emotional_support())}
286 {tr_qa("Use of instrumental support (Q10, Q23)",
287 self.instrumental_support())}
288 {tr_qa("Behavioural disengagement (Q6, Q16)",
289 self.behavioural_disengagement())}
290 {tr_qa("Venting (Q9, Q21)", self.venting())}
291 {tr_qa("Positive reframing (Q12, Q17)",
292 self.positive_reframing())}
293 {tr_qa("Planning (Q14, Q25)", self.planning())}
294 {tr_qa("Humour (Q18, Q28)", self.humour())}
295 {tr_qa("Acceptance (Q20, Q24)", self.acceptance())}
296 {tr_qa("Religion (Q22, Q27)", self.religion())}
297 {tr_qa("Self-blame (Q13, Q26)", self.self_blame())}
298 </table>
299 </div>
300 <div class="{CssClass.EXPLANATION}">
301 Individual items are scored 0–3 (as in Carver 1997 PMID
302 16250744), not 1–4 (as in
303 http://www.psy.miami.edu/faculty/ccarver/sclBrCOPE.html).
304 Summaries, which are all
305 based on two items, are therefore scored 0–6.
306 </div>
307 <table class="{CssClass.TASKDETAIL}">
308 <tr>
309 <th width="50%">Question</th>
310 <th width="50%">Answer</th>
311 </tr>
312 {q_a}
313 </table>
314 """