Hide keyboard shortcuts

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 

2 

3""" 

4camcops_server/tasks/cope.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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. 

16 

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. 

21 

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/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Any, Dict, List, Tuple, Type 

30 

31from sqlalchemy.ext.declarative import DeclarativeMeta 

32from sqlalchemy.sql.schema import Column 

33from sqlalchemy.sql.sqltypes import Integer, UnicodeText 

34 

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) 

50 

51 

52# ============================================================================= 

53# COPE_Brief 

54# ============================================================================= 

55 

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) 

98 

99 

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" 

108 

109 NQUESTIONS = 28 

110 RELATIONSHIP_OTHER_CODE = 0 

111 RELATIONSHIPS_FIRST = 0 

112 RELATIONSHIPS_FIRST_NON_OTHER = 1 

113 RELATIONSHIPS_LAST = 9 

114 

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 ) 

135 

136 @staticmethod 

137 def longname(req: "CamcopsRequest") -> str: 

138 _ = req.gettext 

139 return _("Brief COPE Inventory") 

140 

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 ] 

200 

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 

212 

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 ) 

220 

221 def self_distraction(self) -> int: 

222 return self.sum_fields(["q1", "q19"]) 

223 

224 def active_coping(self) -> int: 

225 return self.sum_fields(["q2", "q7"]) 

226 

227 def denial(self) -> int: 

228 return self.sum_fields(["q3", "q8"]) 

229 

230 def substance_use(self) -> int: 

231 return self.sum_fields(["q4", "q11"]) 

232 

233 def emotional_support(self) -> int: 

234 return self.sum_fields(["q5", "q15"]) 

235 

236 def instrumental_support(self) -> int: 

237 return self.sum_fields(["q10", "q23"]) 

238 

239 def behavioural_disengagement(self) -> int: 

240 return self.sum_fields(["q6", "q16"]) 

241 

242 def venting(self) -> int: 

243 return self.sum_fields(["q9", "q21"]) 

244 

245 def positive_reframing(self) -> int: 

246 return self.sum_fields(["q12", "q17"]) 

247 

248 def planning(self) -> int: 

249 return self.sum_fields(["q14", "q25"]) 

250 

251 def humour(self) -> int: 

252 return self.sum_fields(["q18", "q28"]) 

253 

254 def acceptance(self) -> int: 

255 return self.sum_fields(["q20", "q24"]) 

256 

257 def religion(self) -> int: 

258 return self.sum_fields(["q22", "q27"]) 

259 

260 def self_blame(self) -> int: 

261 return self.sum_fields(["q13", "q26"]) 

262 

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 """