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