Source code for camcops_server.tasks.panss

#!/usr/bin/env python
# camcops_server/tasks/panss.py

"""
===============================================================================

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

    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 <http://www.gnu.org/licenses/>.

===============================================================================
"""

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

from cardinal_pythonlib.stringfunc import strseq
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.sql.sqltypes import Integer

from camcops_server.cc_modules.cc_constants import DATA_COLLECTION_ONLY_DIV
from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
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_request import CamcopsRequest
from camcops_server.cc_modules.cc_summaryelement import SummaryElement
from camcops_server.cc_modules.cc_task import (
    get_from_dict,
    Task,
    TaskHasClinicianMixin,
    TaskHasPatientMixin,
)
from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo


# =============================================================================
# PANSS
# =============================================================================

class PanssMetaclass(DeclarativeMeta):
    # noinspection PyInitNewSignature
    def __init__(cls: Type['Panss'],
                 name: str,
                 bases: Tuple[Type, ...],
                 classdict: Dict[str, Any]) -> None:
        add_multiple_columns(
            cls, "p", 1, cls.NUM_P,
            minimum=1, maximum=7,
            comment_fmt="P{n}: {s} (1 absent - 7 extreme)",
            comment_strings=[
                "delusions", "conceptual disorganisation",
                "hallucinatory behaviour", "excitement",
                "grandiosity", "suspiciousness/persecution",
                "hostility",
            ]
        )
        add_multiple_columns(
            cls, "n", 1, cls.NUM_N,
            minimum=1, maximum=7,
            comment_fmt="N{n}: {s} (1 absent - 7 extreme)",
            comment_strings=[
                "blunted affect", "emotional withdrawal",
                "poor rapport", "passive/apathetic social withdrawal",
                "difficulty in abstract thinking",
                "lack of spontaneity/conversation flow",
                "stereotyped thinking",
            ]
        )
        add_multiple_columns(
            cls, "g", 1, cls.NUM_G,
            minimum=1, maximum=7,
            comment_fmt="G{n}: {s} (1 absent - 7 extreme)",
            comment_strings=[
                "somatic concern",
                "anxiety",
                "guilt feelings",
                "tension",
                "mannerisms/posturing",
                "depression",
                "motor retardation",
                "uncooperativeness",
                "unusual thought content",
                "disorientation",
                "poor attention",
                "lack of judgement/insight",
                "disturbance of volition",
                "poor impulse control",
                "preoccupation",
                "active social avoidance",
            ]
        )
        super().__init__(name, bases, classdict)


[docs]class Panss(TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=PanssMetaclass): __tablename__ = "panss" shortname = "PANSS" longname = "Positive and Negative Syndrome Scale" provides_trackers = True NUM_P = 7 NUM_N = 7 NUM_G = 16 P_FIELDS = strseq("p", 1, NUM_P) N_FIELDS = strseq("n", 1, NUM_N) G_FIELDS = strseq("g", 1, NUM_G) TASK_FIELDS = P_FIELDS + N_FIELDS + G_FIELDS MIN_P = 1 * NUM_P MAX_P = 7 * NUM_P MIN_N = 1 * NUM_N MAX_N = 7 * NUM_N MIN_G = 1 * NUM_G MAX_G = 7 * NUM_G MIN_TOTAL = MIN_P + MIN_N + MIN_G MAX_TOTAL = MAX_P + MAX_N + MAX_G MIN_P_MINUS_N = MIN_P - MAX_N MAX_P_MINUS_N = MAX_P - MIN_N
[docs] def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: return [ TrackerInfo( value=self.total_score(), plot_label="PANSS total score", axis_label="Total score ({}-{})".format( self.MIN_TOTAL, self.MAX_TOTAL), axis_min=self.MIN_TOTAL - 0.5, axis_max=self.MAX_TOTAL + 0.5 ), TrackerInfo( value=self.score_p(), plot_label="PANSS P score", axis_label="P score ({}-{})".format(self.MIN_P, self.MAX_P), axis_min=self.MIN_P - 0.5, axis_max=self.MAX_P + 0.5 ), TrackerInfo( value=self.score_n(), plot_label="PANSS N score", axis_label="N score ({}-{})".format(self.MIN_N, self.MAX_N), axis_min=self.MIN_N - 0.5, axis_max=self.MAX_N + 0.5 ), TrackerInfo( value=self.score_g(), plot_label="PANSS G score", axis_label="G score ({}-{})".format(self.MIN_G, self.MAX_G), axis_min=self.MIN_G - 0.5, axis_max=self.MAX_G + 0.5 ), TrackerInfo( value=self.composite(), plot_label="PANSS composite score ({} to {})".format( self.MIN_P_MINUS_N, self.MAX_P_MINUS_N), axis_label="P - N" ), ]
[docs] def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: if not self.is_complete(): return CTV_INCOMPLETE return [CtvInfo( content=( "PANSS total score {} (P {}, N {}, G {}, " "composite P–N {})".format( self.total_score(), self.score_p(), self.score_n(), self.score_g(), self.composite() ) ) )]
[docs] def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: return self.standard_task_summary_fields() + [ SummaryElement( name="total", coltype=Integer(), value=self.total_score(), comment="Total score ({}-{})".format( self.MIN_TOTAL, self.MAX_TOTAL) ), SummaryElement( name="p", coltype=Integer(), value=self.score_p(), comment="Positive symptom (P) score ({}-{})".format( self.MIN_P, self.MAX_P) ), SummaryElement( name="n", coltype=Integer(), value=self.score_n(), comment="Negative symptom (N) score ({}-{})".format( self.MIN_N, self.MAX_N) ), SummaryElement( name="g", coltype=Integer(), value=self.score_g(), comment="General symptom (G) score ({}-{})".format( self.MIN_G, self.MAX_G) ), SummaryElement( name="composite", coltype=Integer(), value=self.composite(), comment="Composite score (P - N) ({} to {})".format( self.MIN_P_MINUS_N, self.MAX_P_MINUS_N) ), ]
[docs] def is_complete(self) -> bool: return ( self.are_all_fields_complete(self.TASK_FIELDS) and self.field_contents_valid() )
def total_score(self) -> int: return self.sum_fields(self.TASK_FIELDS) def score_p(self) -> int: return self.sum_fields(self.P_FIELDS) def score_n(self) -> int: return self.sum_fields(self.N_FIELDS) def score_g(self) -> int: return self.sum_fields(self.G_FIELDS) def composite(self) -> int: return self.score_p() - self.score_n()
[docs] def get_task_html(self, req: CamcopsRequest) -> str: p = self.score_p() n = self.score_n() g = self.score_g() composite = self.composite() total = p + n + g answers = { None: None, 1: self.wxstring(req, "option1"), 2: self.wxstring(req, "option2"), 3: self.wxstring(req, "option3"), 4: self.wxstring(req, "option4"), 5: self.wxstring(req, "option5"), 6: self.wxstring(req, "option6"), 7: self.wxstring(req, "option7"), } h = """ <div class="summary"> <table class="summary"> """ h += self.get_is_complete_tr(req) h += tr_qa("{} ({}{})".format(req.wappstring("total_score"), self.MIN_TOTAL, self.MAX_TOTAL), total) h += tr_qa("{} ({}{})".format(self.wxstring(req, "p"), self.MIN_P, self.MAX_P), p) h += tr_qa("{} ({}{})".format(self.wxstring(req, "n"), self.MIN_N, self.MAX_N), n) h += tr_qa("{} ({}{})".format(self.wxstring(req, "g"), self.MIN_G, self.MAX_G), g) h += tr_qa("{} ({}{})".format(self.wxstring(req, "composite"), self.MIN_P_MINUS_N, self.MAX_P_MINUS_N), composite) h += """ </table> </div> <table class="taskdetail"> <tr> <th width="40%">Question</th> <th width="60%">Answer</th> </tr> """ for q in self.TASK_FIELDS: h += tr_qa( self.wxstring(req, "" + q + "_s"), get_from_dict(answers, getattr(self, q)) ) h += """ </table> """ + DATA_COLLECTION_ONLY_DIV return h