Coverage for tasks/maas.py: 51%
116 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/maas.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, Optional, Tuple, Type
32from cardinal_pythonlib.classes import classproperty
33from cardinal_pythonlib.stringfunc import strnumlist, strseq
34from sqlalchemy.ext.declarative import DeclarativeMeta
35from sqlalchemy.sql.sqltypes import Integer
37from camcops_server.cc_modules.cc_constants import CssClass
38from camcops_server.cc_modules.cc_db import add_multiple_columns
39from camcops_server.cc_modules.cc_html import tr_qa
40from camcops_server.cc_modules.cc_report import (
41 AverageScoreReport,
42 ScoreDetails,
43)
44from camcops_server.cc_modules.cc_request import CamcopsRequest
45from camcops_server.cc_modules.cc_summaryelement import SummaryElement
46from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
49# =============================================================================
50# MAAS
51# =============================================================================
53QUESTION_SNIPPETS = [
54 # 1-5
55 "thinking about baby",
56 "strength of emotional feelings",
57 "feelings about baby, negative to positive",
58 "desire for info",
59 "picturing baby",
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 # 11-15
67 "emotions about baby, sad to happy",
68 "thoughts of punishing baby",
69 "emotionally distant or close",
70 "good diet",
71 "expectation of feelings after birth",
72 # 16-19
73 "would like to hold baby when",
74 "dreams about baby",
75 "rubbing over baby",
76 "feelings if pregnancy lost",
77]
80class MaasMetaclass(DeclarativeMeta):
81 # noinspection PyInitNewSignature
82 def __init__(
83 cls: Type["Maas"],
84 name: str,
85 bases: Tuple[Type, ...],
86 classdict: Dict[str, Any],
87 ) -> None:
88 add_multiple_columns(
89 cls,
90 cls.FN_QPREFIX,
91 1,
92 cls.N_QUESTIONS,
93 minimum=cls.MIN_SCORE_PER_Q,
94 maximum=cls.MAX_SCORE_PER_Q,
95 comment_fmt="Q{n} ({s}; 1 least attachment - 5 most attachment)",
96 comment_strings=QUESTION_SNIPPETS,
97 )
98 super().__init__(name, bases, classdict)
101class MaasScore(object):
102 def __init__(self) -> None:
103 self.quality_min = 0
104 self.quality_score = 0
105 self.quality_max = 0
106 self.time_min = 0
107 self.time_score = 0
108 self.time_max = 0
109 self.global_min = 0
110 self.global_score = 0
111 self.global_max = 0
113 def add_question(self, qnum: int, score: Optional[int]) -> None:
114 if score is None:
115 return
116 if qnum in Maas.QUALITY_OF_ATTACHMENT_Q:
117 self.quality_min += Maas.MIN_SCORE_PER_Q
118 self.quality_score += score
119 self.quality_max += Maas.MAX_SCORE_PER_Q
120 if qnum in Maas.TIME_IN_ATTACHMENT_MODE_Q:
121 self.time_min += Maas.MIN_SCORE_PER_Q
122 self.time_score += score
123 self.time_max += Maas.MAX_SCORE_PER_Q
124 self.global_min += Maas.MIN_SCORE_PER_Q
125 self.global_score += score
126 self.global_max += Maas.MAX_SCORE_PER_Q
129class Maas(TaskHasPatientMixin, Task, metaclass=MaasMetaclass):
130 """
131 Server implementation of the MAAS task.
132 """
134 __tablename__ = "maas"
135 shortname = "MAAS"
137 FN_QPREFIX = "q"
138 N_QUESTIONS = 19
139 MIN_SCORE_PER_Q = 1
140 MAX_SCORE_PER_Q = 5
141 MIN_GLOBAL = N_QUESTIONS * MIN_SCORE_PER_Q
142 MAX_GLOBAL = N_QUESTIONS * MAX_SCORE_PER_Q
144 TASK_FIELDS = strseq(FN_QPREFIX, 1, N_QUESTIONS)
146 # Questions whose options are presented from 5 to 1, not from 1 to 5:
147 # REVERSED_Q = [1, 3, 5, 6, 7, 9, 10, 12, 15, 16, 18]
149 # Questions that contribute to the "quality of attachment" score:
150 QUALITY_OF_ATTACHMENT_Q = [3, 6, 9, 10, 11, 12, 13, 15, 16, 19]
151 QUALITY_OF_ATTACHMENT_FIELDS = strnumlist(
152 FN_QPREFIX, QUALITY_OF_ATTACHMENT_Q
153 )
154 N_QUALITY = len(QUALITY_OF_ATTACHMENT_Q)
155 MIN_QUALITY = N_QUALITY * MIN_SCORE_PER_Q
156 MAX_QUALITY = N_QUALITY * MAX_SCORE_PER_Q
158 # Questions that contribute to the "time spent in attachment mode" score:
159 TIME_IN_ATTACHMENT_MODE_Q = [1, 2, 4, 5, 8, 14, 17, 18]
160 TIME_IN_ATTACHMENT_FIELDS = strnumlist(
161 FN_QPREFIX, TIME_IN_ATTACHMENT_MODE_Q
162 )
163 N_TIME = len(TIME_IN_ATTACHMENT_MODE_Q)
164 MIN_TIME = N_TIME * MIN_SCORE_PER_Q
165 MAX_TIME = N_TIME * MAX_SCORE_PER_Q
167 @staticmethod
168 def longname(req: "CamcopsRequest") -> str:
169 _ = req.gettext
170 return _("Maternal Antenatal Attachment Scale")
172 def is_complete(self) -> bool:
173 return (
174 self.all_fields_not_none(self.TASK_FIELDS)
175 and self.field_contents_valid()
176 )
178 def get_score(self) -> MaasScore:
179 scorer = MaasScore()
180 for q in range(1, self.N_QUESTIONS + 1):
181 scorer.add_question(q, getattr(self, self.FN_QPREFIX + str(q)))
182 return scorer
184 def get_quality_score(self) -> int:
185 scorer = self.get_score()
186 return scorer.quality_score
188 def get_time_score(self) -> int:
189 scorer = self.get_score()
190 return scorer.time_score
192 def get_global_score(self) -> int:
193 scorer = self.get_score()
194 return scorer.global_score
196 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
197 scorer = self.get_score()
198 return self.standard_task_summary_fields() + [
199 SummaryElement(
200 name="quality_of_attachment_score",
201 coltype=Integer(),
202 value=scorer.quality_score,
203 comment=f"Quality of attachment score (for complete tasks, "
204 f"range "
205 f"{self.MIN_QUALITY}-"
206 f"{self.MAX_QUALITY})",
207 ),
208 SummaryElement(
209 name="time_in_attachment_mode_score",
210 coltype=Integer(),
211 value=scorer.time_score,
212 comment=f"Time spent in attachment mode (or intensity of "
213 f"preoccupation) score (for complete tasks, range "
214 f"{self.MIN_TIME}-"
215 f"{self.MAX_TIME})",
216 ),
217 SummaryElement(
218 name="global_attachment_score",
219 coltype=Integer(),
220 value=scorer.global_score,
221 comment=f"Global attachment score (for complete tasks, range "
222 f"{self.MIN_GLOBAL}-"
223 f"{self.MAX_GLOBAL})",
224 ),
225 ]
227 def get_task_html(self, req: CamcopsRequest) -> str:
228 scorer = self.get_score()
229 quality = tr_qa(
230 self.wxstring(req, "quality_of_attachment_score")
231 + f" [{scorer.quality_min}–{scorer.quality_max}]",
232 scorer.quality_score,
233 )
234 time = tr_qa(
235 self.wxstring(req, "time_in_attachment_mode_score")
236 + f" [{scorer.time_min}–{scorer.time_max}]",
237 scorer.time_score,
238 )
239 globalscore = tr_qa(
240 self.wxstring(req, "global_attachment_score")
241 + f" [{scorer.global_min}–{scorer.global_max}]",
242 scorer.global_score,
243 )
244 lines = [] # type: List[str]
245 for q in range(1, self.N_QUESTIONS + 1):
246 question = f"{q}. " + self.wxstring(req, f"q{q}_q")
247 value = getattr(self, self.FN_QPREFIX + str(q))
248 answer = None
249 if (
250 value is not None
251 and self.MIN_SCORE_PER_Q <= value <= self.MAX_SCORE_PER_Q
252 ):
253 answer = f"{value}: " + self.wxstring(req, f"q{q}_a{value}")
254 lines.append(tr_qa(question, answer))
255 q_a = "".join(lines)
256 return f"""
257 <div class="{CssClass.SUMMARY}">
258 <table class="{CssClass.SUMMARY}">
259 {self.get_is_complete_tr(req)}
260 {quality}
261 {time}
262 {globalscore}
263 </table>
264 </div>
265 <table class="{CssClass.TASKDETAIL}">
266 <tr>
267 <th width="60%">Question</th>
268 <th width="40%">Answer</th>
269 </tr>
270 {q_a}
271 </table>
272 <div class="{CssClass.EXPLANATION}">
273 Ratings for each question are from {self.MIN_SCORE_PER_Q} (lowest
274 attachment) to {self.MAX_SCORE_PER_Q} (highest attachment). The
275 quality of attachment score is the sum of questions
276 {self.QUALITY_OF_ATTACHMENT_Q}. The “time spent in attachment mode”
277 score is the sum of questions {self.TIME_IN_ATTACHMENT_MODE_Q}. The
278 global score is the sum of all questions.
279 </div>
280 <div class="{CssClass.FOOTNOTES}">
281 Condon, J. (2015). Maternal Antenatal Attachment Scale
282 [Measurement instrument]. Retrieved from <a
283 href="https://hdl.handle.net/2328/35292">https://hdl.handle.net/2328/35292</a>.
285 Copyright © John T Condon 2015. This is an Open Access article
286 distributed under the terms of the Creative Commons Attribution
287 License 3.0 AU (<a
288 href="https://creativecommons.org/licenses/by/3.0">https://creativecommons.org/licenses/by/3.0</a>),
289 which permits unrestricted use, distribution, and reproduction in
290 any medium, provided the original work is properly cited.
291 </div>
292 """
295class MaasReport(AverageScoreReport):
296 # noinspection PyMethodParameters
297 @classproperty
298 def report_id(cls) -> str:
299 return "MAAS"
301 @classmethod
302 def title(cls, req: "CamcopsRequest") -> str:
303 _ = req.gettext
304 return _("MAAS — Average scores")
306 # noinspection PyMethodParameters
307 @classproperty
308 def task_class(cls) -> Type[Task]:
309 return Maas
311 @classmethod
312 def scoretypes(cls, req: "CamcopsRequest") -> List[ScoreDetails]:
313 _ = req.gettext
314 return [
315 ScoreDetails(
316 name=_("Global attachment score"),
317 scorefunc=Maas.get_global_score,
318 minimum=Maas.MIN_GLOBAL,
319 maximum=Maas.MAX_GLOBAL,
320 higher_score_is_better=True,
321 ),
322 ScoreDetails(
323 name=_("Quality of attachment score"),
324 scorefunc=Maas.get_quality_score,
325 minimum=Maas.MIN_QUALITY,
326 maximum=Maas.MAX_QUALITY,
327 higher_score_is_better=True,
328 ),
329 ScoreDetails(
330 name=_("Time spent in attachment mode"),
331 scorefunc=Maas.get_time_score,
332 minimum=Maas.MIN_TIME,
333 maximum=Maas.MAX_TIME,
334 higher_score_is_better=True,
335 ),
336 ]