Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/dast.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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

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

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

15 (at your option) any later version. 

16 

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

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

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

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

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

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Any, Dict, List, Tuple, Type 

30 

31from cardinal_pythonlib.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.sqltypes import Integer 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

37from camcops_server.cc_modules.cc_db import add_multiple_columns 

38from camcops_server.cc_modules.cc_html import ( 

39 answer, 

40 get_yes_no, 

41 tr, 

42 tr_qa, 

43) 

44from camcops_server.cc_modules.cc_request import CamcopsRequest 

45from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

46from camcops_server.cc_modules.cc_sqla_coltypes import CharColType 

47from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

48from camcops_server.cc_modules.cc_task import ( 

49 get_from_dict, 

50 Task, 

51 TaskHasPatientMixin, 

52) 

53from camcops_server.cc_modules.cc_text import SS 

54from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

55 

56 

57# ============================================================================= 

58# DAST 

59# ============================================================================= 

60 

61class DastMetaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__(cls: Type['Dast'], 

64 name: str, 

65 bases: Tuple[Type, ...], 

66 classdict: Dict[str, Any]) -> None: 

67 add_multiple_columns( 

68 cls, "q", 1, cls.NQUESTIONS, CharColType, 

69 pv=['Y', 'N'], 

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

71 comment_strings=[ 

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

73 "abused prescription drugs (+)", 

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

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

76 "stop when want to (-)", 

77 "abuse drugs continuously (+)", 

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

79 "blackouts/flashbacks (+)", 

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

81 "spouse/parents complain (+)", 

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

83 "caused problems with spouse (+)", 

84 "family sought help (+)", 

85 "lost friends (+)", 

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

87 "trouble at work (+)", 

88 "lost job (+)", 

89 "fights under influence (+)", 

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

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

92 "illegal activities to obtain (+)", 

93 "arrested for possession (+)", 

94 "withdrawal symptoms (+)", 

95 "medical problems (+)", 

96 "sought help (+)", 

97 "hospital for medical problems (+)", 

98 "drug treatment program (+)", 

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

100 ] 

101 ) 

102 super().__init__(name, bases, classdict) 

103 

104 

105class Dast(TaskHasPatientMixin, Task, 

106 metaclass=DastMetaclass): 

107 """ 

108 Server implementation of the DAST task. 

109 """ 

110 __tablename__ = "dast" 

111 shortname = "DAST" 

112 provides_trackers = True 

113 

114 prohibits_commercial = True 

115 

116 NQUESTIONS = 28 

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

118 

119 @staticmethod 

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

121 _ = req.gettext 

122 return _("Drug Abuse Screening Test") 

123 

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

125 return [TrackerInfo( 

126 value=self.total_score(), 

127 plot_label="DAST total score", 

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

129 axis_min=-0.5, 

130 axis_max=self.NQUESTIONS + 0.5, 

131 horizontal_lines=[10.5, 5.5] 

132 )] 

133 

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

135 if not self.is_complete(): 

136 return CTV_INCOMPLETE 

137 return [CtvInfo( 

138 content=f"DAST total score {self.total_score()}/{self.NQUESTIONS}" 

139 )] 

140 

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

142 return self.standard_task_summary_fields() + [ 

143 SummaryElement(name="total", 

144 coltype=Integer(), 

145 value=self.total_score(), 

146 comment="Total score"), 

147 ] 

148 

149 def is_complete(self) -> bool: 

150 return ( 

151 self.all_fields_not_none(Dast.TASK_FIELDS) and 

152 self.field_contents_valid() 

153 ) 

154 

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

156 yes = "Y" 

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

158 if value is None: 

159 return 0 

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

161 return 0 if value == yes else 1 

162 else: 

163 return 1 if value == yes else 0 

164 

165 def total_score(self) -> int: 

166 total = 0 

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

168 total += self.get_score(q) 

169 return total 

170 

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

172 score = self.total_score() 

173 exceeds_cutoff_1 = score >= 6 

174 exceeds_cutoff_2 = score >= 11 

175 main_dict = { 

176 None: None, 

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

178 "N": req.sstring(SS.NO) 

179 } 

180 q_a = "" 

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

182 q_a += tr( 

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

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

185 " — " + answer(str(self.get_score(q))) 

186 ) 

187 

188 h = """ 

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

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

191 {tr_is_complete} 

192 {total_score} 

193 {exceeds_standard_cutoff_1} 

194 {exceeds_standard_cutoff_2} 

195 </table> 

196 </div> 

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

198 <tr> 

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

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

201 </tr> 

202 {q_a} 

203 </table> 

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

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

206 Addiction and Mental Health, Toronto, Canada. 

207 Reproduced here under the permissions granted for 

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

209 copyright holder for any other use. 

210 </div> 

211 """.format( 

212 CssClass=CssClass, 

213 tr_is_complete=self.get_is_complete_tr(req), 

214 total_score=tr(req.sstring(SS.TOTAL_SCORE), 

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

216 exceeds_standard_cutoff_1=tr_qa( 

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

218 get_yes_no(req, exceeds_cutoff_1) 

219 ), 

220 exceeds_standard_cutoff_2=tr_qa( 

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

222 get_yes_no(req, exceeds_cutoff_2) 

223 ), 

224 q_a=q_a, 

225 ) 

226 return h 

227 

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

229 if not self.is_complete(): 

230 return [] 

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