Coverage for tasks/hama.py: 55%
64 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/hama.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 CtvInfo, CTV_INCOMPLETE
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 SummaryCategoryColType
42from camcops_server.cc_modules.cc_summaryelement import SummaryElement
43from camcops_server.cc_modules.cc_task import (
44 get_from_dict,
45 Task,
46 TaskHasClinicianMixin,
47 TaskHasPatientMixin,
48)
49from camcops_server.cc_modules.cc_text import SS
50from camcops_server.cc_modules.cc_trackerhelpers import (
51 TrackerInfo,
52 TrackerLabel,
53)
56# =============================================================================
57# HAM-A
58# =============================================================================
61class HamaMetaclass(DeclarativeMeta):
62 # noinspection PyInitNewSignature
63 def __init__(
64 cls: Type["Hama"],
65 name: str,
66 bases: Tuple[Type, ...],
67 classdict: Dict[str, Any],
68 ) -> None:
69 add_multiple_columns(
70 cls,
71 "q",
72 1,
73 cls.NQUESTIONS,
74 comment_fmt="Q{n}, {s} (0-4, higher worse)",
75 minimum=0,
76 maximum=4,
77 comment_strings=[
78 "anxious mood",
79 "tension",
80 "fears",
81 "insomnia",
82 "concentration/memory",
83 "depressed mood",
84 "somatic, muscular",
85 "somatic, sensory",
86 "cardiovascular",
87 "respiratory",
88 "gastrointestinal",
89 "genitourinary",
90 "other autonomic",
91 "behaviour in interview",
92 ],
93 )
94 super().__init__(name, bases, classdict)
97class Hama(
98 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=HamaMetaclass
99):
100 """
101 Server implementation of the HAM-A task.
102 """
104 __tablename__ = "hama"
105 shortname = "HAM-A"
106 provides_trackers = True
108 NQUESTIONS = 14
109 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
110 MAX_SCORE = 56
112 @staticmethod
113 def longname(req: "CamcopsRequest") -> str:
114 _ = req.gettext
115 return _("Hamilton Rating Scale for Anxiety")
117 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
118 return [
119 TrackerInfo(
120 value=self.total_score(),
121 plot_label="HAM-A 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=[30.5, 24.5, 17.5],
126 horizontal_labels=[
127 TrackerLabel(33, req.sstring(SS.VERY_SEVERE)),
128 TrackerLabel(27.5, req.sstring(SS.MODERATE_TO_SEVERE)),
129 TrackerLabel(21, req.sstring(SS.MILD_TO_MODERATE)),
130 TrackerLabel(8.75, req.sstring(SS.MILD)),
131 ],
132 )
133 ]
135 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
136 if not self.is_complete():
137 return CTV_INCOMPLETE
138 return [
139 CtvInfo(
140 content=(
141 f"HAM-A total score {self.total_score()}/{self.MAX_SCORE} "
142 f"({self.severity(req)})"
143 )
144 )
145 ]
147 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
148 return self.standard_task_summary_fields() + [
149 SummaryElement(
150 name="total",
151 coltype=Integer(),
152 value=self.total_score(),
153 comment=f"Total score (/{self.MAX_SCORE})",
154 ),
155 SummaryElement(
156 name="severity",
157 coltype=SummaryCategoryColType,
158 value=self.severity(req),
159 comment="Severity",
160 ),
161 ]
163 def is_complete(self) -> bool:
164 return (
165 self.all_fields_not_none(self.TASK_FIELDS)
166 and self.field_contents_valid()
167 )
169 def total_score(self) -> int:
170 return self.sum_fields(self.TASK_FIELDS)
172 def severity(self, req: CamcopsRequest) -> str:
173 score = self.total_score()
174 if score >= 31:
175 return req.sstring(SS.VERY_SEVERE)
176 elif score >= 25:
177 return req.sstring(SS.MODERATE_TO_SEVERE)
178 elif score >= 18:
179 return req.sstring(SS.MILD_TO_MODERATE)
180 else:
181 return req.sstring(SS.MILD)
183 def get_task_html(self, req: CamcopsRequest) -> str:
184 score = self.total_score()
185 severity = self.severity(req)
186 answer_dicts = []
187 for q in range(1, self.NQUESTIONS + 1):
188 d = {None: None}
189 for option in range(0, 4 + 1):
190 d[option] = self.wxstring(
191 req, "q" + str(q) + "_option" + str(option)
192 )
193 answer_dicts.append(d)
194 q_a = ""
195 for q in range(1, self.NQUESTIONS + 1):
196 q_a += tr_qa(
197 self.wxstring(req, "q" + str(q) + "_s")
198 + " "
199 + self.wxstring(req, "q" + str(q) + "_question"),
200 get_from_dict(
201 answer_dicts[q - 1], getattr(self, "q" + str(q))
202 ),
203 )
204 return """
205 <div class="{CssClass.SUMMARY}">
206 <table class="{CssClass.SUMMARY}">
207 {tr_is_complete}
208 {total_score}
209 {symptom_severity}
210 </table>
211 </div>
212 <table class="{CssClass.TASKDETAIL}">
213 <tr>
214 <th width="50%">Question</th>
215 <th width="50%">Answer</th>
216 </tr>
217 {q_a}
218 </table>
219 <div class="{CssClass.FOOTNOTES}">
220 [1] ≥31 very severe, ≥25 moderate to severe,
221 ≥18 mild to moderate, otherwise mild.
222 </div>
223 """.format(
224 CssClass=CssClass,
225 tr_is_complete=self.get_is_complete_tr(req),
226 total_score=tr(
227 req.sstring(SS.TOTAL_SCORE),
228 answer(score) + " / {}".format(self.MAX_SCORE),
229 ),
230 symptom_severity=tr_qa(
231 self.wxstring(req, "symptom_severity") + " <sup>[1]</sup>",
232 severity,
233 ),
234 q_a=q_a,
235 )