Coverage for tasks/maas.py : 51%

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/maas.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, Optional, Tuple, Type
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
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
48# =============================================================================
49# MAAS
50# =============================================================================
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",
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",
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",
74 # 16-19
75 "would like to hold baby when",
76 "dreams about baby",
77 "rubbing over baby",
78 "feelings if pregnancy lost",
79]
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)
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
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
126class Maas(TaskHasPatientMixin, Task,
127 metaclass=MaasMetaclass):
128 """
129 Server implementation of the MAAS task.
130 """
131 __tablename__ = "maas"
132 shortname = "MAAS"
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
141 TASK_FIELDS = strseq(FN_QPREFIX, 1, N_QUESTIONS)
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]
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
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
162 @staticmethod
163 def longname(req: "CamcopsRequest") -> str:
164 _ = req.gettext
165 return _("Maternal Antenatal Attachment Scale")
167 def is_complete(self) -> bool:
168 return (
169 self.all_fields_not_none(self.TASK_FIELDS) and
170 self.field_contents_valid()
171 )
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
179 def get_quality_score(self) -> int:
180 scorer = self.get_score()
181 return scorer.quality_score
183 def get_time_score(self) -> int:
184 scorer = self.get_score()
185 return scorer.time_score
187 def get_global_score(self) -> int:
188 scorer = self.get_score()
189 return scorer.global_score
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 ]
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>.
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 """
279class MaasReport(AverageScoreReport):
280 # noinspection PyMethodParameters
281 @classproperty
282 def report_id(cls) -> str:
283 return "MAAS"
285 @classmethod
286 def title(cls, req: "CamcopsRequest") -> str:
287 _ = req.gettext
288 return _("MAAS — Average scores")
290 # noinspection PyMethodParameters
291 @classproperty
292 def task_class(cls) -> Type[Task]:
293 return Maas
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 ]