Coverage for tasks/hamd7.py : 54%

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/hamd7.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, Tuple, Type
31from cardinal_pythonlib.stringfunc import strseq
32from sqlalchemy.ext.declarative import DeclarativeMeta
33from sqlalchemy.sql.sqltypes import Integer
35from camcops_server.cc_modules.cc_constants import CssClass
36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
37from camcops_server.cc_modules.cc_db import add_multiple_columns
38from camcops_server.cc_modules.cc_html import answer, tr, tr_qa
39from camcops_server.cc_modules.cc_request import CamcopsRequest
40from camcops_server.cc_modules.cc_sqla_coltypes import (
41 SummaryCategoryColType,
42 ZERO_TO_TWO_CHECKER,
43)
44from camcops_server.cc_modules.cc_summaryelement import SummaryElement
45from camcops_server.cc_modules.cc_task import (
46 get_from_dict,
47 Task,
48 TaskHasClinicianMixin,
49 TaskHasPatientMixin,
50)
51from camcops_server.cc_modules.cc_text import SS
52from camcops_server.cc_modules.cc_trackerhelpers import (
53 TrackerInfo,
54 TrackerLabel,
55)
58# =============================================================================
59# HAMD-7
60# =============================================================================
62class Hamd7Metaclass(DeclarativeMeta):
63 # noinspection PyInitNewSignature,PyUnresolvedReferences
64 def __init__(cls: Type['Hamd7'],
65 name: str,
66 bases: Tuple[Type, ...],
67 classdict: Dict[str, Any]) -> None:
68 add_multiple_columns(
69 cls, "q", 1, cls.NQUESTIONS,
70 minimum=0, maximum=4, # see below
71 comment_fmt="Q{n}, {s} (0-4, except Q6 0-2; higher worse)",
72 comment_strings=["depressed mood", "guilt",
73 "interest/pleasure/level of activities",
74 "psychological anxiety", "somatic anxiety",
75 "energy/somatic symptoms", "suicide"]
76 )
77 # Now fix the wrong bits. Hardly elegant!
78 cls.q6.set_permitted_value_checker(ZERO_TO_TWO_CHECKER)
80 super().__init__(name, bases, classdict)
83class Hamd7(TaskHasPatientMixin, TaskHasClinicianMixin, Task,
84 metaclass=Hamd7Metaclass):
85 """
86 Server implementation of the HAMD-7 task.
87 """
88 __tablename__ = "hamd7"
89 shortname = "HAMD-7"
90 provides_trackers = True
92 NQUESTIONS = 7
93 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
94 MAX_SCORE = 26
96 @staticmethod
97 def longname(req: "CamcopsRequest") -> str:
98 _ = req.gettext
99 return _("Hamilton Rating Scale for Depression (7-item scale)")
101 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
102 return [TrackerInfo(
103 value=self.total_score(),
104 plot_label="HAM-D-7 total score",
105 axis_label=f"Total score (out of {self.MAX_SCORE})",
106 axis_min=-0.5,
107 axis_max=self.MAX_SCORE + 0.5,
108 horizontal_lines=[19.5, 11.5, 3.5],
109 horizontal_labels=[
110 TrackerLabel(23, self.wxstring(req, "severity_severe")),
111 TrackerLabel(15.5, self.wxstring(req, "severity_moderate")),
112 TrackerLabel(7.5, self.wxstring(req, "severity_mild")),
113 TrackerLabel(1.75, self.wxstring(req, "severity_none")),
114 ]
115 )]
117 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
118 if not self.is_complete():
119 return CTV_INCOMPLETE
120 return [CtvInfo(content=(
121 f"HAM-D-7 total score {self.total_score()}/{self.MAX_SCORE} "
122 f"({self.severity(req)})"
123 ))]
125 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
126 return self.standard_task_summary_fields() + [
127 SummaryElement(name="total",
128 coltype=Integer(),
129 value=self.total_score(),
130 comment=f"Total score (/{self.MAX_SCORE})"),
131 SummaryElement(name="severity",
132 coltype=SummaryCategoryColType,
133 value=self.severity(req),
134 comment="Severity"),
135 ]
137 def is_complete(self) -> bool:
138 return (
139 self.all_fields_not_none(self.TASK_FIELDS) and
140 self.field_contents_valid()
141 )
143 def total_score(self) -> int:
144 return self.sum_fields(self.TASK_FIELDS)
146 def severity(self, req: CamcopsRequest) -> str:
147 score = self.total_score()
148 if score >= 20:
149 return self.wxstring(req, "severity_severe")
150 elif score >= 12:
151 return self.wxstring(req, "severity_moderate")
152 elif score >= 4:
153 return self.wxstring(req, "severity_mild")
154 else:
155 return self.wxstring(req, "severity_none")
157 def get_task_html(self, req: CamcopsRequest) -> str:
158 score = self.total_score()
159 severity = self.severity(req)
160 answer_dicts = []
161 for q in range(1, self.NQUESTIONS + 1):
162 d = {None: None}
163 for option in range(0, 5):
164 if q == 6 and option > 2:
165 continue
166 d[option] = self.wxstring(req, "q" + str(q) + "_option" +
167 str(option))
168 answer_dicts.append(d)
170 q_a = ""
171 for q in range(1, self.NQUESTIONS + 1):
172 q_a += tr_qa(
173 self.wxstring(req, "q" + str(q) + "_s"),
174 get_from_dict(answer_dicts[q - 1], getattr(self, "q" + str(q)))
175 )
177 return """
178 <div class="{CssClass.SUMMARY}">
179 <table class="{CssClass.SUMMARY}">
180 {tr_is_complete}
181 {total_score}
182 {severity}
183 </table>
184 </div>
185 <table class="{CssClass.TASKDETAIL}">
186 <tr>
187 <th width="30%">Question</th>
188 <th width="70%">Answer</th>
189 </tr>
190 {q_a}
191 </table>
192 <div class="{CssClass.FOOTNOTES}">
193 [1] ≥20 severe, ≥12 moderate, ≥4 mild, <4 none.
194 </div>
195 """.format(
196 CssClass=CssClass,
197 tr_is_complete=self.get_is_complete_tr(req),
198 total_score=tr(
199 req.sstring(SS.TOTAL_SCORE),
200 answer(score) + " / {}".format(self.MAX_SCORE)
201 ),
202 severity=tr_qa(
203 self.wxstring(req, "severity") + " <sup>[1]</sup>",
204 severity
205 ),
206 q_a=q_a,
207 )