Coverage for tasks/smast.py: 51%
74 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/smast.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_snomed import SnomedExpression, SnomedLookup
42from camcops_server.cc_modules.cc_sqla_coltypes import (
43 CharColType,
44 SummaryCategoryColType,
45)
46from camcops_server.cc_modules.cc_summaryelement import SummaryElement
47from camcops_server.cc_modules.cc_task import (
48 get_from_dict,
49 Task,
50 TaskHasPatientMixin,
51)
52from camcops_server.cc_modules.cc_text import SS
53from camcops_server.cc_modules.cc_trackerhelpers import (
54 TrackerLabel,
55 TrackerInfo,
56)
59# =============================================================================
60# SMAST
61# =============================================================================
64class SmastMetaclass(DeclarativeMeta):
65 # noinspection PyInitNewSignature
66 def __init__(
67 cls: Type["Smast"],
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 CharColType,
78 pv=["Y", "N"],
79 comment_fmt="Q{n}: {s} (Y or N)",
80 comment_strings=[
81 "believe you are a normal drinker",
82 "near relative worries/complains",
83 "feel guilty",
84 "friends/relative think you are a normal drinker",
85 "stop when you want to",
86 "ever attended Alcoholics Anonymous",
87 "problems with close relative",
88 "trouble at work",
89 "neglected obligations for >=2 days",
90 "sought help",
91 "hospitalized",
92 "arrested for drink-driving",
93 "arrested for other drunken behaviour",
94 ],
95 )
96 super().__init__(name, bases, classdict)
99class Smast(TaskHasPatientMixin, Task, metaclass=SmastMetaclass):
100 """
101 Server implementation of the SMAST task.
102 """
104 __tablename__ = "smast"
105 shortname = "SMAST"
106 info_filename_stem = "mast"
107 provides_trackers = True
109 NQUESTIONS = 13
110 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
112 @staticmethod
113 def longname(req: "CamcopsRequest") -> str:
114 _ = req.gettext
115 return _("Short Michigan Alcohol Screening Test")
117 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
118 return [
119 TrackerInfo(
120 value=self.total_score(),
121 plot_label="SMAST total score",
122 axis_label=f"Total score (out of {self.NQUESTIONS})",
123 axis_min=-0.5,
124 axis_max=self.NQUESTIONS + 0.5,
125 horizontal_lines=[2.5, 1.5],
126 horizontal_labels=[
127 TrackerLabel(4, self.wxstring(req, "problem_probable")),
128 TrackerLabel(2, self.wxstring(req, "problem_possible")),
129 TrackerLabel(0.75, self.wxstring(req, "problem_unlikely")),
130 ],
131 )
132 ]
134 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
135 if not self.is_complete():
136 return CTV_INCOMPLETE
137 return [
138 CtvInfo(
139 content=(
140 f"SMAST total score {self.total_score()}/{self.NQUESTIONS} " # noqa: E501
141 f"({self.likelihood(req)})"
142 )
143 )
144 ]
146 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
147 return self.standard_task_summary_fields() + [
148 SummaryElement(
149 name="total",
150 coltype=Integer(),
151 value=self.total_score(),
152 comment=f"Total score (/{self.NQUESTIONS})",
153 ),
154 SummaryElement(
155 name="likelihood",
156 coltype=SummaryCategoryColType,
157 value=self.likelihood(req),
158 comment="Likelihood of problem",
159 ),
160 ]
162 def is_complete(self) -> bool:
163 return (
164 self.all_fields_not_none(self.TASK_FIELDS)
165 and self.field_contents_valid()
166 )
168 def get_score(self, q: int) -> int:
169 yes = "Y"
170 value = getattr(self, "q" + str(q))
171 if value is None:
172 return 0
173 if q == 1 or q == 4 or q == 5:
174 return 0 if value == yes else 1
175 else:
176 return 1 if value == yes else 0
178 def total_score(self) -> int:
179 total = 0
180 for q in range(1, self.NQUESTIONS + 1):
181 total += self.get_score(q)
182 return total
184 def likelihood(self, req: CamcopsRequest) -> str:
185 score = self.total_score()
186 if score >= 3:
187 return self.wxstring(req, "problem_probable")
188 elif score >= 2:
189 return self.wxstring(req, "problem_possible")
190 else:
191 return self.wxstring(req, "problem_unlikely")
193 def get_task_html(self, req: CamcopsRequest) -> str:
194 score = self.total_score()
195 likelihood = self.likelihood(req)
196 main_dict = {
197 None: None,
198 "Y": req.sstring(SS.YES),
199 "N": req.sstring(SS.NO),
200 }
201 q_a = ""
202 for q in range(1, self.NQUESTIONS + 1):
203 q_a += tr(
204 self.wxstring(req, "q" + str(q)),
205 answer(get_from_dict(main_dict, getattr(self, "q" + str(q))))
206 + " — "
207 + str(self.get_score(q)),
208 )
209 h = """
210 <div class="{CssClass.SUMMARY}">
211 <table class="{CssClass.SUMMARY}">
212 {tr_is_complete}
213 {total_score}
214 {problem_likelihood}
215 </table>
216 </div>
217 <table class="{CssClass.TASKDETAIL}">
218 <tr>
219 <th width="80%">Question</th>
220 <th width="20%">Answer</th>
221 </tr>
222 {q_a}
223 </table>
224 <div class="{CssClass.FOOTNOTES}">
225 [1] Total score ≥3 probable, ≥2 possible, 0–1 unlikely.
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) + f" / {self.NQUESTIONS}",
233 ),
234 problem_likelihood=tr_qa(
235 self.wxstring(req, "problem_likelihood") + " <sup>[1]</sup>",
236 likelihood,
237 ),
238 q_a=q_a,
239 )
240 return h
242 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
243 if not self.is_complete():
244 return []
245 return [SnomedExpression(req.snomed(SnomedLookup.SMAST_SCALE))]