Source code for camcops_server.tasks.maas

#!/usr/bin/env python

"""
camcops_server/tasks/maas.py

===============================================================================

    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

"""

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

from cardinal_pythonlib.classes import classproperty
from cardinal_pythonlib.stringfunc import strnumlist, strseq
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.sql.sqltypes import Integer

from camcops_server.cc_modules.cc_constants import CssClass
from camcops_server.cc_modules.cc_db import add_multiple_columns
from camcops_server.cc_modules.cc_html import tr_qa
from camcops_server.cc_modules.cc_report import (
    AverageScoreReport,
    ScoreDetails,
)
from camcops_server.cc_modules.cc_request import CamcopsRequest
from camcops_server.cc_modules.cc_summaryelement import SummaryElement
from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin


# =============================================================================
# MAAS
# =============================================================================

QUESTION_SNIPPETS = [
    # 1-5
    "thinking about baby",
    "strength of emotional feelings",
    "feelings about baby, negative to positive",
    "desire for info",
    "picturing baby",
    # 6-10
    "baby's personhood",
    "baby depends on me",
    "talking to baby",
    "thoughts, irritation to tender/loving",
    "clarity of mental picture",
    # 11-15
    "emotions about baby, sad to happy",
    "thoughts of punishing baby",
    "emotionally distant or close",
    "good diet",
    "expectation of feelings after birth",
    # 16-19
    "would like to hold baby when",
    "dreams about baby",
    "rubbing over baby",
    "feelings if pregnancy lost",
]


class MaasMetaclass(DeclarativeMeta):
    # noinspection PyInitNewSignature
    def __init__(
        cls: Type["Maas"],
        name: str,
        bases: Tuple[Type, ...],
        classdict: Dict[str, Any],
    ) -> None:
        add_multiple_columns(
            cls,
            cls.FN_QPREFIX,
            1,
            cls.N_QUESTIONS,
            minimum=cls.MIN_SCORE_PER_Q,
            maximum=cls.MAX_SCORE_PER_Q,
            comment_fmt="Q{n} ({s}; 1 least attachment - 5 most attachment)",
            comment_strings=QUESTION_SNIPPETS,
        )
        super().__init__(name, bases, classdict)


class MaasScore(object):
    def __init__(self) -> None:
        self.quality_min = 0
        self.quality_score = 0
        self.quality_max = 0
        self.time_min = 0
        self.time_score = 0
        self.time_max = 0
        self.global_min = 0
        self.global_score = 0
        self.global_max = 0

    def add_question(self, qnum: int, score: Optional[int]) -> None:
        if score is None:
            return
        if qnum in Maas.QUALITY_OF_ATTACHMENT_Q:
            self.quality_min += Maas.MIN_SCORE_PER_Q
            self.quality_score += score
            self.quality_max += Maas.MAX_SCORE_PER_Q
        if qnum in Maas.TIME_IN_ATTACHMENT_MODE_Q:
            self.time_min += Maas.MIN_SCORE_PER_Q
            self.time_score += score
            self.time_max += Maas.MAX_SCORE_PER_Q
        self.global_min += Maas.MIN_SCORE_PER_Q
        self.global_score += score
        self.global_max += Maas.MAX_SCORE_PER_Q


[docs]class Maas(TaskHasPatientMixin, Task, metaclass=MaasMetaclass): """ Server implementation of the MAAS task. """ __tablename__ = "maas" shortname = "MAAS" FN_QPREFIX = "q" N_QUESTIONS = 19 MIN_SCORE_PER_Q = 1 MAX_SCORE_PER_Q = 5 MIN_GLOBAL = N_QUESTIONS * MIN_SCORE_PER_Q MAX_GLOBAL = N_QUESTIONS * MAX_SCORE_PER_Q TASK_FIELDS = strseq(FN_QPREFIX, 1, N_QUESTIONS) # Questions whose options are presented from 5 to 1, not from 1 to 5: # REVERSED_Q = [1, 3, 5, 6, 7, 9, 10, 12, 15, 16, 18] # Questions that contribute to the "quality of attachment" score: QUALITY_OF_ATTACHMENT_Q = [3, 6, 9, 10, 11, 12, 13, 15, 16, 19] QUALITY_OF_ATTACHMENT_FIELDS = strnumlist( FN_QPREFIX, QUALITY_OF_ATTACHMENT_Q ) N_QUALITY = len(QUALITY_OF_ATTACHMENT_Q) MIN_QUALITY = N_QUALITY * MIN_SCORE_PER_Q MAX_QUALITY = N_QUALITY * MAX_SCORE_PER_Q # Questions that contribute to the "time spent in attachment mode" score: TIME_IN_ATTACHMENT_MODE_Q = [1, 2, 4, 5, 8, 14, 17, 18] TIME_IN_ATTACHMENT_FIELDS = strnumlist( FN_QPREFIX, TIME_IN_ATTACHMENT_MODE_Q ) N_TIME = len(TIME_IN_ATTACHMENT_MODE_Q) MIN_TIME = N_TIME * MIN_SCORE_PER_Q MAX_TIME = N_TIME * MAX_SCORE_PER_Q
[docs] @staticmethod def longname(req: "CamcopsRequest") -> str: _ = req.gettext return _("Maternal Antenatal Attachment Scale")
[docs] def is_complete(self) -> bool: return ( self.all_fields_not_none(self.TASK_FIELDS) and self.field_contents_valid() )
def get_score(self) -> MaasScore: scorer = MaasScore() for q in range(1, self.N_QUESTIONS + 1): scorer.add_question(q, getattr(self, self.FN_QPREFIX + str(q))) return scorer def get_quality_score(self) -> int: scorer = self.get_score() return scorer.quality_score def get_time_score(self) -> int: scorer = self.get_score() return scorer.time_score def get_global_score(self) -> int: scorer = self.get_score() return scorer.global_score
[docs] def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: scorer = self.get_score() return self.standard_task_summary_fields() + [ SummaryElement( name="quality_of_attachment_score", coltype=Integer(), value=scorer.quality_score, comment=f"Quality of attachment score (for complete tasks, " f"range " f"{self.MIN_QUALITY}-" f"{self.MAX_QUALITY})", ), SummaryElement( name="time_in_attachment_mode_score", coltype=Integer(), value=scorer.time_score, comment=f"Time spent in attachment mode (or intensity of " f"preoccupation) score (for complete tasks, range " f"{self.MIN_TIME}-" f"{self.MAX_TIME})", ), SummaryElement( name="global_attachment_score", coltype=Integer(), value=scorer.global_score, comment=f"Global attachment score (for complete tasks, range " f"{self.MIN_GLOBAL}-" f"{self.MAX_GLOBAL})", ), ]
[docs] def get_task_html(self, req: CamcopsRequest) -> str: scorer = self.get_score() quality = tr_qa( self.wxstring(req, "quality_of_attachment_score") + f" [{scorer.quality_min}{scorer.quality_max}]", scorer.quality_score, ) time = tr_qa( self.wxstring(req, "time_in_attachment_mode_score") + f" [{scorer.time_min}{scorer.time_max}]", scorer.time_score, ) globalscore = tr_qa( self.wxstring(req, "global_attachment_score") + f" [{scorer.global_min}{scorer.global_max}]", scorer.global_score, ) lines = [] # type: List[str] for q in range(1, self.N_QUESTIONS + 1): question = f"{q}. " + self.wxstring(req, f"q{q}_q") value = getattr(self, self.FN_QPREFIX + str(q)) answer = None if ( value is not None and self.MIN_SCORE_PER_Q <= value <= self.MAX_SCORE_PER_Q ): answer = f"{value}: " + self.wxstring(req, f"q{q}_a{value}") lines.append(tr_qa(question, answer)) q_a = "".join(lines) return f""" <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {self.get_is_complete_tr(req)} {quality} {time} {globalscore} </table> </div> <table class="{CssClass.TASKDETAIL}"> <tr> <th width="60%">Question</th> <th width="40%">Answer</th> </tr> {q_a} </table> <div class="{CssClass.EXPLANATION}"> Ratings for each question are from {self.MIN_SCORE_PER_Q} (lowest attachment) to {self.MAX_SCORE_PER_Q} (highest attachment). The quality of attachment score is the sum of questions {self.QUALITY_OF_ATTACHMENT_Q}. The “time spent in attachment mode” score is the sum of questions {self.TIME_IN_ATTACHMENT_MODE_Q}. The global score is the sum of all questions. </div> <div class="{CssClass.FOOTNOTES}"> Condon, J. (2015). Maternal Antenatal Attachment Scale [Measurement instrument]. Retrieved from <a href="https://hdl.handle.net/2328/35292">https://hdl.handle.net/2328/35292</a>. Copyright © John T Condon 2015. This is an Open Access article distributed under the terms of the Creative Commons Attribution License 3.0 AU (<a href="https://creativecommons.org/licenses/by/3.0">https://creativecommons.org/licenses/by/3.0</a>), which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. </div> """
[docs]class MaasReport(AverageScoreReport): # noinspection PyMethodParameters @classproperty def report_id(cls) -> str: return "MAAS" @classmethod def title(cls, req: "CamcopsRequest") -> str: _ = req.gettext return _("MAAS — Average scores") # noinspection PyMethodParameters
[docs] @classproperty def task_class(cls) -> Type[Task]: return Maas
@classmethod def scoretypes(cls, req: "CamcopsRequest") -> List[ScoreDetails]: _ = req.gettext return [ ScoreDetails( name=_("Global attachment score"), scorefunc=Maas.get_global_score, minimum=Maas.MIN_GLOBAL, maximum=Maas.MAX_GLOBAL, higher_score_is_better=True, ), ScoreDetails( name=_("Quality of attachment score"), scorefunc=Maas.get_quality_score, minimum=Maas.MIN_QUALITY, maximum=Maas.MAX_QUALITY, higher_score_is_better=True, ), ScoreDetails( name=_("Time spent in attachment mode"), scorefunc=Maas.get_time_score, minimum=Maas.MIN_TIME, maximum=Maas.MAX_TIME, higher_score_is_better=True, ), ]