Coverage for tasks/dast.py: 52%

65 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_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 

28from typing import Any, List, Type 

29 

30from cardinal_pythonlib.stringfunc import strseq 

31from sqlalchemy.sql.sqltypes import Integer 

32 

33from camcops_server.cc_modules.cc_constants import CssClass 

34from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

35from camcops_server.cc_modules.cc_db import add_multiple_columns 

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

37from camcops_server.cc_modules.cc_request import CamcopsRequest 

38from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

39from camcops_server.cc_modules.cc_sqla_coltypes import CharColType 

40from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

41from camcops_server.cc_modules.cc_task import ( 

42 get_from_dict, 

43 Task, 

44 TaskHasPatientMixin, 

45) 

46from camcops_server.cc_modules.cc_text import SS 

47from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

48 

49 

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

51# DAST 

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

53 

54 

55class 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))]