Coverage for tasks/dast.py: 52%

1"""

2 camcops_server/tasks/dast.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

11 CamCOPS is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License as published by

13 the Free Software Foundation, either version 3 of the License, or

14 (at your option) any later version.

15

16 CamCOPS is distributed in the hope that it will be useful,

17 but WITHOUT ANY WARRANTY; without even the implied warranty of

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

19 GNU General Public License for more details.

20

21 You should have received a copy of the GNU General Public License

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

23

24===============================================================================

25

26"""

27

28 from typing import Any, List, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31 from sqlalchemy.sql.sqltypes import Integer

32

33 from camcops_server.cc_modules.cc_constants import CssClass

34 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

35 from camcops_server.cc_modules.cc_db import add_multiple_columns

36 from camcops_server.cc_modules.cc_html import answer, get_yes_no, tr, tr_qa

37 from camcops_server.cc_modules.cc_request import CamcopsRequest

38 from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup

39 from camcops_server.cc_modules.cc_sqla_coltypes import CharColType

40 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

41 from camcops_server.cc_modules.cc_task import (

42 get_from_dict,

43 Task,

44 TaskHasPatientMixin,

45)

46 from camcops_server.cc_modules.cc_text import SS

47 from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo

48

49

50# =============================================================================

51# DAST

52# =============================================================================

53

54

55 class Dast( # type: ignore[misc]

56 TaskHasPatientMixin,

57 Task,

58):

59 """

60 Server implementation of the DAST task.

61 """

62

63 __tablename__ = "dast"

64 shortname = "DAST"

65 provides_trackers = True

66

67 prohibits_commercial = True

68

69 NQUESTIONS = 28

70

71 @classmethod

72 def extend_columns(cls: Type["Dast"], **kwargs: Any) -> None:

73 add_multiple_columns(

74 cls,

75 "q",

76 1,

77 cls.NQUESTIONS,

78 CharColType,

79 pv=["Y", "N"],

80 comment_fmt='Q{n}. {s} ("+" = Y scores 1, "-" = N scores 1)',

81 comment_strings=[

82 "non-medical drug use (+)",

83 "abused prescription drugs (+)",

84 "abused >1 drug at a time (+)",

85 "get through week without drugs (-)",

86 "stop when want to (-)",

87 "abuse drugs continuously (+)",

88 "try to limit to certain situations (-)",

89 "blackouts/flashbacks (+)",

90 "feel bad about drug abuse (-)",

91 "spouse/parents complain (+)",

92 "friends/relative know/suspect (+)",

93 "caused problems with spouse (+)",

94 "family sought help (+)",

95 "lost friends (+)",

96 "neglected family/missed work (+)",

97 "trouble at work (+)",

98 "lost job (+)",

99 "fights under influence (+)",

100 "arrested for unusual behaviour under influence (+)",

101 "arrested for driving under influence (+)",

102 "illegal activities to obtain (+)",

103 "arrested for possession (+)",

104 "withdrawal symptoms (+)",

105 "medical problems (+)",

106 "sought help (+)",

107 "hospital for medical problems (+)",

108 "drug treatment program (+)",

109 "outpatient treatment for drug abuse (+)",

110 ],

111 )

112

113 TASK_FIELDS = strseq("q", 1, NQUESTIONS)

114

115 @staticmethod

116 def longname(req: "CamcopsRequest") -> str:

117 _ = req.gettext

118 return _("Drug Abuse Screening Test")

119

120 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:

121 return [

122 TrackerInfo(

123 value=self.total_score(),

124 plot_label="DAST total score",

125 axis_label=f"Total score (out of {self.NQUESTIONS})",

126 axis_min=-0.5,

127 axis_max=self.NQUESTIONS + 0.5,

128 horizontal_lines=[10.5, 5.5],

129 )

130 ]

131

132 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:

133 if not self.is_complete():

134 return CTV_INCOMPLETE

135 return [

136 CtvInfo(

137 content=f"DAST total score "

138 f"{self.total_score()}/{self.NQUESTIONS}"

139 )

140 ]

141

142 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:

143 return self.standard_task_summary_fields() + [

144 SummaryElement(

145 name="total",

146 coltype=Integer(),

147 value=self.total_score(),

148 comment="Total score",

149 )

150 ]

151

152 def is_complete(self) -> bool:

153 return (

154 self.all_fields_not_none(Dast.TASK_FIELDS)

155 and self.field_contents_valid()

156 )

157

158 def get_score(self, q: int) -> int:

159 yes = "Y"

160 value = getattr(self, "q" + str(q))

161 if value is None:

162 return 0

163 if q == 4 or q == 5 or q == 7:

164 return 0 if value == yes else 1

165 else:

166 return 1 if value == yes else 0

167

168 def total_score(self) -> int:

169 total = 0

170 for q in range(1, Dast.NQUESTIONS + 1):

171 total += self.get_score(q)

172 return total

173

174 def get_task_html(self, req: CamcopsRequest) -> str:

175 score = self.total_score()

176 exceeds_cutoff_1 = score >= 6

177 exceeds_cutoff_2 = score >= 11

178 main_dict = {

179 None: None,

180 "Y": req.sstring(SS.YES),

181 "N": req.sstring(SS.NO),

182 }

183 q_a = ""

184 for q in range(1, Dast.NQUESTIONS + 1):

185 q_a += tr(

186 self.wxstring(req, "q" + str(q)),

187 answer(get_from_dict(main_dict, getattr(self, "q" + str(q))))

188 + " — "

189 + answer(str(self.get_score(q))),

190 )

191

192 h = """

193 <div class="{CssClass.SUMMARY}">

194 <table class="{CssClass.SUMMARY}">

195 {tr_is_complete}

196 {total_score}

197 {exceeds_standard_cutoff_1}

198 {exceeds_standard_cutoff_2}

199 </table>

200 </div>

201 <table class="{CssClass.TASKDETAIL}">

202 <tr>

203 <th width="80%">Question</th>

204 <th width="20%">Answer</th>

205 </tr>

206 {q_a}

207 </table>

208 <div class="{CssClass.COPYRIGHT}">

209 DAST: Copyright © Harvey A. Skinner and the Centre for

210 Addiction and Mental Health, Toronto, Canada.

211 Reproduced here under the permissions granted for

212 NON-COMMERCIAL use only. You must obtain permission from the

213 copyright holder for any other use.

214 </div>

215 """.format(

216 CssClass=CssClass,

217 tr_is_complete=self.get_is_complete_tr(req),

218 total_score=tr(

219 req.sstring(SS.TOTAL_SCORE),

220 answer(score) + f" / {self.NQUESTIONS}",

221 ),

222 exceeds_standard_cutoff_1=tr_qa(

223 self.wxstring(req, "exceeds_standard_cutoff_1"),

224 get_yes_no(req, exceeds_cutoff_1),

225 ),

226 exceeds_standard_cutoff_2=tr_qa(

227 self.wxstring(req, "exceeds_standard_cutoff_2"),

228 get_yes_no(req, exceeds_cutoff_2),

229 ),

230 q_a=q_a,

231 )

232 return h

233

234 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:

235 if not self.is_complete():

236 return []

237 return [SnomedExpression(req.snomed(SnomedLookup.DAST_SCALE))]