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/smast.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 CtvInfo, CTV_INCOMPLETE 

37from camcops_server.cc_modules.cc_db import add_multiple_columns 

38from camcops_server.cc_modules.cc_html import answer, tr, tr_qa 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

41from camcops_server.cc_modules.cc_sqla_coltypes import ( 

42 CharColType, 

43 SummaryCategoryColType, 

44) 

45from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

46from camcops_server.cc_modules.cc_task import ( 

47 get_from_dict, 

48 Task, 

49 TaskHasPatientMixin, 

50) 

51from camcops_server.cc_modules.cc_text import SS 

52from camcops_server.cc_modules.cc_trackerhelpers import ( 

53 TrackerLabel, 

54 TrackerInfo, 

55) 

56 

57 

58# ============================================================================= 

59# SMAST 

60# ============================================================================= 

61 

62class SmastMetaclass(DeclarativeMeta): 

63 # noinspection PyInitNewSignature 

64 def __init__(cls: Type['Smast'], 

65 name: str, 

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

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

68 add_multiple_columns( 

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

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

71 comment_fmt="Q{n}: {s} (Y or N)", 

72 comment_strings=[ 

73 "believe you are a normal drinker", 

74 "near relative worries/complains", 

75 "feel guilty", 

76 "friends/relative think you are a normal drinker", 

77 "stop when you want to", 

78 "ever attended Alcoholics Anonymous", 

79 "problems with close relative", 

80 "trouble at work", 

81 "neglected obligations for >=2 days", 

82 "sought help", 

83 "hospitalized", 

84 "arrested for drink-driving", 

85 "arrested for other drunken behaviour", 

86 ] 

87 ) 

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

89 

90 

91class Smast(TaskHasPatientMixin, Task, 

92 metaclass=SmastMetaclass): 

93 """ 

94 Server implementation of the SMAST task. 

95 """ 

96 __tablename__ = "smast" 

97 shortname = "SMAST" 

98 provides_trackers = True 

99 

100 NQUESTIONS = 13 

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

102 

103 @staticmethod 

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

105 _ = req.gettext 

106 return _("Short Michigan Alcohol Screening Test") 

107 

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

109 return [TrackerInfo( 

110 value=self.total_score(), 

111 plot_label="SMAST total score", 

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

113 axis_min=-0.5, 

114 axis_max=self.NQUESTIONS + 0.5, 

115 horizontal_lines=[ 

116 2.5, 

117 1.5, 

118 ], 

119 horizontal_labels=[ 

120 TrackerLabel(4, self.wxstring(req, "problem_probable")), 

121 TrackerLabel(2, self.wxstring(req, "problem_possible")), 

122 TrackerLabel(0.75, self.wxstring(req, "problem_unlikely")), 

123 ] 

124 )] 

125 

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

127 if not self.is_complete(): 

128 return CTV_INCOMPLETE 

129 return [CtvInfo(content=( 

130 f"SMAST total score {self.total_score()}/{self.NQUESTIONS} " 

131 f"({self.likelihood(req)})" 

132 ))] 

133 

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

135 return self.standard_task_summary_fields() + [ 

136 SummaryElement( 

137 name="total", 

138 coltype=Integer(), 

139 value=self.total_score(), 

140 comment=f"Total score (/{self.NQUESTIONS})"), 

141 SummaryElement( 

142 name="likelihood", 

143 coltype=SummaryCategoryColType, 

144 value=self.likelihood(req), 

145 comment="Likelihood of problem"), 

146 ] 

147 

148 def is_complete(self) -> bool: 

149 return ( 

150 self.all_fields_not_none(self.TASK_FIELDS) and 

151 self.field_contents_valid() 

152 ) 

153 

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

155 yes = "Y" 

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

157 if value is None: 

158 return 0 

159 if q == 1 or q == 4 or q == 5: 

160 return 0 if value == yes else 1 

161 else: 

162 return 1 if value == yes else 0 

163 

164 def total_score(self) -> int: 

165 total = 0 

166 for q in range(1, self.NQUESTIONS + 1): 

167 total += self.get_score(q) 

168 return total 

169 

170 def likelihood(self, req: CamcopsRequest) -> str: 

171 score = self.total_score() 

172 if score >= 3: 

173 return self.wxstring(req, "problem_probable") 

174 elif score >= 2: 

175 return self.wxstring(req, "problem_possible") 

176 else: 

177 return self.wxstring(req, "problem_unlikely") 

178 

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

180 score = self.total_score() 

181 likelihood = self.likelihood(req) 

182 main_dict = { 

183 None: None, 

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

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

186 } 

187 q_a = "" 

188 for q in range(1, self.NQUESTIONS + 1): 

189 q_a += tr( 

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

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

192 " — " + str(self.get_score(q)) 

193 ) 

194 h = """ 

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

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

197 {tr_is_complete} 

198 {total_score} 

199 {problem_likelihood} 

200 </table> 

201 </div> 

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

203 <tr> 

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

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

206 </tr> 

207 {q_a} 

208 </table> 

209 <div class="{CssClass.FOOTNOTES}"> 

210 [1] Total score ≥3 probable, ≥2 possible, 0–1 unlikely. 

211 </div> 

212 """.format( 

213 CssClass=CssClass, 

214 tr_is_complete=self.get_is_complete_tr(req), 

215 total_score=tr( 

216 req.sstring(SS.TOTAL_SCORE), 

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

218 ), 

219 problem_likelihood=tr_qa( 

220 self.wxstring(req, "problem_likelihood") + " <sup>[1]</sup>", 

221 likelihood 

222 ), 

223 q_a=q_a, 

224 ) 

225 return h 

226 

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

228 if not self.is_complete(): 

229 return [] 

230 return [SnomedExpression(req.snomed(SnomedLookup.SMAST_SCALE))]