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/maas.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, Optional, Tuple, Type 

30 

31from cardinal_pythonlib.classes import classproperty 

32from cardinal_pythonlib.stringfunc import strnumlist, strseq 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34from sqlalchemy.sql.sqltypes import Integer 

35 

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_report import ( 

40 AverageScoreReport, 

41 ScoreDetails, 

42) 

43from camcops_server.cc_modules.cc_request import CamcopsRequest 

44from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

45from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

46 

47 

48# ============================================================================= 

49# MAAS 

50# ============================================================================= 

51 

52QUESTION_SNIPPETS = [ 

53 # 1-5 

54 "thinking about baby", 

55 "strength of emotional feelings", 

56 "feelings about baby, negative to positive", 

57 "desire for info", 

58 "picturing baby", 

59 

60 # 6-10 

61 "baby's personhood", 

62 "baby depends on me", 

63 "talking to baby", 

64 "thoughts, irritation to tender/loving", 

65 "clarity of mental picture", 

66 

67 # 11-15 

68 "emotions about baby, sad to happy", 

69 "thoughts of punishing baby", 

70 "emotionally distant or close", 

71 "good diet", 

72 "expectation of feelings after birth", 

73 

74 # 16-19 

75 "would like to hold baby when", 

76 "dreams about baby", 

77 "rubbing over baby", 

78 "feelings if pregnancy lost", 

79] 

80 

81 

82class MaasMetaclass(DeclarativeMeta): 

83 # noinspection PyInitNewSignature 

84 def __init__(cls: Type['Maas'], 

85 name: str, 

86 bases: Tuple[Type, ...], 

87 classdict: Dict[str, Any]) -> None: 

88 add_multiple_columns( 

89 cls, cls.FN_QPREFIX, 1, cls.N_QUESTIONS, 

90 minimum=cls.MIN_SCORE_PER_Q, 

91 maximum=cls.MAX_SCORE_PER_Q, 

92 comment_fmt="Q{n} ({s}; 1 least attachment - 5 most attachment)", 

93 comment_strings=QUESTION_SNIPPETS 

94 ) 

95 super().__init__(name, bases, classdict) 

96 

97 

98class MaasScore(object): 

99 def __init__(self) -> None: 

100 self.quality_min = 0 

101 self.quality_score = 0 

102 self.quality_max = 0 

103 self.time_min = 0 

104 self.time_score = 0 

105 self.time_max = 0 

106 self.global_min = 0 

107 self.global_score = 0 

108 self.global_max = 0 

109 

110 def add_question(self, qnum: int, score: Optional[int]) -> None: 

111 if score is None: 

112 return 

113 if qnum in Maas.QUALITY_OF_ATTACHMENT_Q: 

114 self.quality_min += Maas.MIN_SCORE_PER_Q 

115 self.quality_score += score 

116 self.quality_max += Maas.MAX_SCORE_PER_Q 

117 if qnum in Maas.TIME_IN_ATTACHMENT_MODE_Q: 

118 self.time_min += Maas.MIN_SCORE_PER_Q 

119 self.time_score += score 

120 self.time_max += Maas.MAX_SCORE_PER_Q 

121 self.global_min += Maas.MIN_SCORE_PER_Q 

122 self.global_score += score 

123 self.global_max += Maas.MAX_SCORE_PER_Q 

124 

125 

126class Maas(TaskHasPatientMixin, Task, 

127 metaclass=MaasMetaclass): 

128 """ 

129 Server implementation of the MAAS task. 

130 """ 

131 __tablename__ = "maas" 

132 shortname = "MAAS" 

133 

134 FN_QPREFIX = "q" 

135 N_QUESTIONS = 19 

136 MIN_SCORE_PER_Q = 1 

137 MAX_SCORE_PER_Q = 5 

138 MIN_GLOBAL = N_QUESTIONS * MIN_SCORE_PER_Q 

139 MAX_GLOBAL = N_QUESTIONS * MAX_SCORE_PER_Q 

140 

141 TASK_FIELDS = strseq(FN_QPREFIX, 1, N_QUESTIONS) 

142 

143 # Questions whose options are presented from 5 to 1, not from 1 to 5: 

144 # REVERSED_Q = [1, 3, 5, 6, 7, 9, 10, 12, 15, 16, 18] 

145 

146 # Questions that contribute to the "quality of attachment" score: 

147 QUALITY_OF_ATTACHMENT_Q = [3, 6, 9, 10, 11, 12, 13, 15, 16, 19] 

148 QUALITY_OF_ATTACHMENT_FIELDS = strnumlist(FN_QPREFIX, 

149 QUALITY_OF_ATTACHMENT_Q) 

150 N_QUALITY = len(QUALITY_OF_ATTACHMENT_Q) 

151 MIN_QUALITY = N_QUALITY * MIN_SCORE_PER_Q 

152 MAX_QUALITY = N_QUALITY * MAX_SCORE_PER_Q 

153 

154 # Questions that contribute to the "time spent in attachment mode" score: 

155 TIME_IN_ATTACHMENT_MODE_Q = [1, 2, 4, 5, 8, 14, 17, 18] 

156 TIME_IN_ATTACHMENT_FIELDS = strnumlist(FN_QPREFIX, 

157 TIME_IN_ATTACHMENT_MODE_Q) 

158 N_TIME = len(TIME_IN_ATTACHMENT_MODE_Q) 

159 MIN_TIME = N_TIME * MIN_SCORE_PER_Q 

160 MAX_TIME = N_TIME * MAX_SCORE_PER_Q 

161 

162 @staticmethod 

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

164 _ = req.gettext 

165 return _("Maternal Antenatal Attachment Scale") 

166 

167 def is_complete(self) -> bool: 

168 return ( 

169 self.all_fields_not_none(self.TASK_FIELDS) and 

170 self.field_contents_valid() 

171 ) 

172 

173 def get_score(self) -> MaasScore: 

174 scorer = MaasScore() 

175 for q in range(1, self.N_QUESTIONS + 1): 

176 scorer.add_question(q, getattr(self, self.FN_QPREFIX + str(q))) 

177 return scorer 

178 

179 def get_quality_score(self) -> int: 

180 scorer = self.get_score() 

181 return scorer.quality_score 

182 

183 def get_time_score(self) -> int: 

184 scorer = self.get_score() 

185 return scorer.time_score 

186 

187 def get_global_score(self) -> int: 

188 scorer = self.get_score() 

189 return scorer.global_score 

190 

191 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

192 scorer = self.get_score() 

193 return self.standard_task_summary_fields() + [ 

194 SummaryElement( 

195 name="quality_of_attachment_score", coltype=Integer(), 

196 value=scorer.quality_score, 

197 comment=f"Quality of attachment score (for complete tasks, " 

198 f"range " 

199 f"{self.MIN_QUALITY}-" 

200 f"{self.MAX_QUALITY})"), 

201 SummaryElement( 

202 name="time_in_attachment_mode_score", coltype=Integer(), 

203 value=scorer.time_score, 

204 comment=f"Time spent in attachment mode (or intensity of " 

205 f"preoccupation) score (for complete tasks, range " 

206 f"{self.MIN_TIME}-" 

207 f"{self.MAX_TIME})"), 

208 SummaryElement( 

209 name="global_attachment_score", coltype=Integer(), 

210 value=scorer.global_score, 

211 comment=f"Global attachment score (for complete tasks, range " 

212 f"{self.MIN_GLOBAL}-" 

213 f"{self.MAX_GLOBAL})"), 

214 ] 

215 

216 def get_task_html(self, req: CamcopsRequest) -> str: 

217 scorer = self.get_score() 

218 quality = tr_qa( 

219 self.wxstring(req, "quality_of_attachment_score") + 

220 f" [{scorer.quality_min}–{scorer.quality_max}]", 

221 scorer.quality_score) 

222 time = tr_qa( 

223 self.wxstring(req, "time_in_attachment_mode_score") + 

224 f" [{scorer.time_min}–{scorer.time_max}]", 

225 scorer.time_score) 

226 globalscore = tr_qa( 

227 self.wxstring(req, "global_attachment_score") + 

228 f" [{scorer.global_min}–{scorer.global_max}]", 

229 scorer.global_score) 

230 lines = [] # type: List[str] 

231 for q in range(1, self.N_QUESTIONS + 1): 

232 question = f"{q}. " + self.wxstring(req, f"q{q}_q") 

233 value = getattr(self, self.FN_QPREFIX + str(q)) 

234 answer = None 

235 if (value is not None and 

236 self.MIN_SCORE_PER_Q <= value <= self.MAX_SCORE_PER_Q): 

237 answer = f"{value}: " + self.wxstring(req, f"q{q}_a{value}") 

238 lines.append(tr_qa(question, answer)) 

239 q_a = "".join(lines) 

240 return f""" 

241 <div class="{CssClass.SUMMARY}"> 

242 <table class="{CssClass.SUMMARY}"> 

243 {self.get_is_complete_tr(req)} 

244 {quality} 

245 {time} 

246 {globalscore} 

247 </table> 

248 </div> 

249 <table class="{CssClass.TASKDETAIL}"> 

250 <tr> 

251 <th width="60%">Question</th> 

252 <th width="40%">Answer</th> 

253 </tr> 

254 {q_a} 

255 </table> 

256 <div class="{CssClass.EXPLANATION}"> 

257 Ratings for each question are from {self.MIN_SCORE_PER_Q} (lowest 

258 attachment) to {self.MAX_SCORE_PER_Q} (highest attachment). The 

259 quality of attachment score is the sum of questions 

260 {self.QUALITY_OF_ATTACHMENT_Q}. The “time spent in attachment mode” 

261 score is the sum of questions {self.TIME_IN_ATTACHMENT_MODE_Q}. The 

262 global score is the sum of all questions. 

263 </div> 

264 <div class="{CssClass.FOOTNOTES}"> 

265 Condon, J. (2015). Maternal Antenatal Attachment Scale 

266 [Measurement instrument]. Retrieved from <a 

267 href="https://hdl.handle.net/2328/35292">https://hdl.handle.net/2328/35292</a>. 

268 

269 Copyright © John T Condon 2015. This is an Open Access article 

270 distributed under the terms of the Creative Commons Attribution 

271 License 3.0 AU (<a 

272 href="https://creativecommons.org/licenses/by/3.0">https://creativecommons.org/licenses/by/3.0</a>), 

273 which permits unrestricted use, distribution, and reproduction in 

274 any medium, provided the original work is properly cited. 

275 </div> 

276 """ 

277 

278 

279class MaasReport(AverageScoreReport): 

280 # noinspection PyMethodParameters 

281 @classproperty 

282 def report_id(cls) -> str: 

283 return "MAAS" 

284 

285 @classmethod 

286 def title(cls, req: "CamcopsRequest") -> str: 

287 _ = req.gettext 

288 return _("MAAS — Average scores") 

289 

290 # noinspection PyMethodParameters 

291 @classproperty 

292 def task_class(cls) -> Type[Task]: 

293 return Maas 

294 

295 @classmethod 

296 def scoretypes(cls, req: "CamcopsRequest") -> List[ScoreDetails]: 

297 _ = req.gettext 

298 return [ 

299 ScoreDetails( 

300 name=_("Global attachment score"), 

301 scorefunc=Maas.get_global_score, 

302 minimum=Maas.MIN_GLOBAL, 

303 maximum=Maas.MAX_GLOBAL, 

304 higher_score_is_better=True 

305 ), 

306 ScoreDetails( 

307 name=_("Quality of attachment score"), 

308 scorefunc=Maas.get_quality_score, 

309 minimum=Maas.MIN_QUALITY, 

310 maximum=Maas.MAX_QUALITY, 

311 higher_score_is_better=True 

312 ), 

313 ScoreDetails( 

314 name=_("Time spent in attachment mode"), 

315 scorefunc=Maas.get_time_score, 

316 minimum=Maas.MIN_TIME, 

317 maximum=Maas.MAX_TIME, 

318 higher_score_is_better=True 

319 ) 

320 ]