Coverage for tasks/dast.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/dast.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, get_yes_no, 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 CharColType
43from camcops_server.cc_modules.cc_summaryelement import SummaryElement
44from camcops_server.cc_modules.cc_task import (
45 get_from_dict,
46 Task,
47 TaskHasPatientMixin,
48)
49from camcops_server.cc_modules.cc_text import SS
50from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
53# =============================================================================
54# DAST
55# =============================================================================
58class DastMetaclass(DeclarativeMeta):
59 # noinspection PyInitNewSignature
60 def __init__(
61 cls: Type["Dast"],
62 name: str,
63 bases: Tuple[Type, ...],
64 classdict: Dict[str, Any],
65 ) -> None:
66 add_multiple_columns(
67 cls,
68 "q",
69 1,
70 cls.NQUESTIONS,
71 CharColType,
72 pv=["Y", "N"],
73 comment_fmt='Q{n}. {s} ("+" = Y scores 1, "-" = N scores 1)',
74 comment_strings=[
75 "non-medical drug use (+)",
76 "abused prescription drugs (+)",
77 "abused >1 drug at a time (+)",
78 "get through week without drugs (-)",
79 "stop when want to (-)",
80 "abuse drugs continuously (+)",
81 "try to limit to certain situations (-)",
82 "blackouts/flashbacks (+)",
83 "feel bad about drug abuse (-)",
84 "spouse/parents complain (+)",
85 "friends/relative know/suspect (+)",
86 "caused problems with spouse (+)",
87 "family sought help (+)",
88 "lost friends (+)",
89 "neglected family/missed work (+)",
90 "trouble at work (+)",
91 "lost job (+)",
92 "fights under influence (+)",
93 "arrested for unusual behaviour under influence (+)",
94 "arrested for driving under influence (+)",
95 "illegal activities to obtain (+)",
96 "arrested for possession (+)",
97 "withdrawal symptoms (+)",
98 "medical problems (+)",
99 "sought help (+)",
100 "hospital for medical problems (+)",
101 "drug treatment program (+)",
102 "outpatient treatment for drug abuse (+)",
103 ],
104 )
105 super().__init__(name, bases, classdict)
108class Dast(TaskHasPatientMixin, Task, metaclass=DastMetaclass):
109 """
110 Server implementation of the DAST task.
111 """
113 __tablename__ = "dast"
114 shortname = "DAST"
115 provides_trackers = True
117 prohibits_commercial = True
119 NQUESTIONS = 28
120 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
122 @staticmethod
123 def longname(req: "CamcopsRequest") -> str:
124 _ = req.gettext
125 return _("Drug Abuse Screening Test")
127 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
128 return [
129 TrackerInfo(
130 value=self.total_score(),
131 plot_label="DAST total score",
132 axis_label=f"Total score (out of {self.NQUESTIONS})",
133 axis_min=-0.5,
134 axis_max=self.NQUESTIONS + 0.5,
135 horizontal_lines=[10.5, 5.5],
136 )
137 ]
139 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
140 if not self.is_complete():
141 return CTV_INCOMPLETE
142 return [
143 CtvInfo(
144 content=f"DAST total score "
145 f"{self.total_score()}/{self.NQUESTIONS}"
146 )
147 ]
149 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
150 return self.standard_task_summary_fields() + [
151 SummaryElement(
152 name="total",
153 coltype=Integer(),
154 value=self.total_score(),
155 comment="Total score",
156 )
157 ]
159 def is_complete(self) -> bool:
160 return (
161 self.all_fields_not_none(Dast.TASK_FIELDS)
162 and self.field_contents_valid()
163 )
165 def get_score(self, q: int) -> int:
166 yes = "Y"
167 value = getattr(self, "q" + str(q))
168 if value is None:
169 return 0
170 if q == 4 or q == 5 or q == 7:
171 return 0 if value == yes else 1
172 else:
173 return 1 if value == yes else 0
175 def total_score(self) -> int:
176 total = 0
177 for q in range(1, Dast.NQUESTIONS + 1):
178 total += self.get_score(q)
179 return total
181 def get_task_html(self, req: CamcopsRequest) -> str:
182 score = self.total_score()
183 exceeds_cutoff_1 = score >= 6
184 exceeds_cutoff_2 = score >= 11
185 main_dict = {
186 None: None,
187 "Y": req.sstring(SS.YES),
188 "N": req.sstring(SS.NO),
189 }
190 q_a = ""
191 for q in range(1, Dast.NQUESTIONS + 1):
192 q_a += tr(
193 self.wxstring(req, "q" + str(q)),
194 answer(get_from_dict(main_dict, getattr(self, "q" + str(q))))
195 + " — "
196 + answer(str(self.get_score(q))),
197 )
199 h = """
200 <div class="{CssClass.SUMMARY}">
201 <table class="{CssClass.SUMMARY}">
202 {tr_is_complete}
203 {total_score}
204 {exceeds_standard_cutoff_1}
205 {exceeds_standard_cutoff_2}
206 </table>
207 </div>
208 <table class="{CssClass.TASKDETAIL}">
209 <tr>
210 <th width="80%">Question</th>
211 <th width="20%">Answer</th>
212 </tr>
213 {q_a}
214 </table>
215 <div class="{CssClass.COPYRIGHT}">
216 DAST: Copyright © Harvey A. Skinner and the Centre for
217 Addiction and Mental Health, Toronto, Canada.
218 Reproduced here under the permissions granted for
219 NON-COMMERCIAL use only. You must obtain permission from the
220 copyright holder for any other use.
221 </div>
222 """.format(
223 CssClass=CssClass,
224 tr_is_complete=self.get_is_complete_tr(req),
225 total_score=tr(
226 req.sstring(SS.TOTAL_SCORE),
227 answer(score) + f" / {self.NQUESTIONS}",
228 ),
229 exceeds_standard_cutoff_1=tr_qa(
230 self.wxstring(req, "exceeds_standard_cutoff_1"),
231 get_yes_no(req, exceeds_cutoff_1),
232 ),
233 exceeds_standard_cutoff_2=tr_qa(
234 self.wxstring(req, "exceeds_standard_cutoff_2"),
235 get_yes_no(req, exceeds_cutoff_2),
236 ),
237 q_a=q_a,
238 )
239 return h
241 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
242 if not self.is_complete():
243 return []
244 return [SnomedExpression(req.snomed(SnomedLookup.DAST_SCALE))]