Coverage for tasks/gad7.py: 54%

68 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/gad7.py 

5 

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

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28""" 

29 

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

31 

32from cardinal_pythonlib.stringfunc import strseq 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34from sqlalchemy.sql.sqltypes import Integer 

35 

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, 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 SummaryCategoryColType 

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 ( 

51 TrackerInfo, 

52 TrackerLabel, 

53) 

54 

55 

56# ============================================================================= 

57# GAD-7 

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

59 

60 

61class Gad7Metaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__( 

64 cls: Type["Gad7"], 

65 name: str, 

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

67 classdict: Dict[str, Any], 

68 ) -> None: 

69 add_multiple_columns( 

70 cls, 

71 "q", 

72 1, 

73 cls.NQUESTIONS, 

74 minimum=0, 

75 maximum=3, 

76 comment_fmt="Q{n}, {s} (0 not at all - 3 nearly every day)", 

77 comment_strings=[ 

78 "nervous/anxious/on edge", 

79 "can't stop/control worrying", 

80 "worrying too much about different things", 

81 "trouble relaxing", 

82 "restless", 

83 "irritable", 

84 "afraid", 

85 ], 

86 ) 

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

88 

89 

90class Gad7(TaskHasPatientMixin, Task, metaclass=Gad7Metaclass): 

91 """ 

92 Server implementation of the GAD-7 task. 

93 """ 

94 

95 __tablename__ = "gad7" 

96 shortname = "GAD-7" 

97 provides_trackers = True 

98 

99 NQUESTIONS = 7 

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

101 MAX_SCORE = 21 

102 

103 @staticmethod 

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

105 _ = req.gettext 

106 return _("Generalized Anxiety Disorder Assessment") 

107 

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

109 return [ 

110 TrackerInfo( 

111 value=self.total_score(), 

112 plot_label="GAD-7 total score", 

113 axis_label="Total score (out of 21)", 

114 axis_min=-0.5, 

115 axis_max=self.MAX_SCORE + 0.5, 

116 horizontal_lines=[14.5, 9.5, 4.5], 

117 horizontal_labels=[ 

118 TrackerLabel(17, req.sstring(SS.SEVERE)), 

119 TrackerLabel(12, req.sstring(SS.MODERATE)), 

120 TrackerLabel(7, req.sstring(SS.MILD)), 

121 TrackerLabel(2.25, req.sstring(SS.NONE)), 

122 ], 

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 [ 

130 CtvInfo( 

131 content=( 

132 f"GAD-7 total score {self.total_score()}/{self.MAX_SCORE} " 

133 f"({self.severity(req)})" 

134 ) 

135 ) 

136 ] 

137 

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

139 return self.standard_task_summary_fields() + [ 

140 SummaryElement( 

141 name="total", 

142 coltype=Integer(), 

143 value=self.total_score(), 

144 comment=f"Total score (/{self.MAX_SCORE})", 

145 ), 

146 SummaryElement( 

147 name="severity", 

148 coltype=SummaryCategoryColType, 

149 value=self.severity(req), 

150 comment="Severity", 

151 ), 

152 ] 

153 

154 def is_complete(self) -> bool: 

155 return ( 

156 self.all_fields_not_none(self.TASK_FIELDS) 

157 and self.field_contents_valid() 

158 ) 

159 

160 def total_score(self) -> int: 

161 return self.sum_fields(self.TASK_FIELDS) 

162 

163 def severity(self, req: CamcopsRequest) -> str: 

164 score = self.total_score() 

165 if score >= 15: 

166 severity = req.sstring(SS.SEVERE) 

167 elif score >= 10: 

168 severity = req.sstring(SS.MODERATE) 

169 elif score >= 5: 

170 severity = req.sstring(SS.MILD) 

171 else: 

172 severity = req.sstring(SS.NONE) 

173 return severity 

174 

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

176 score = self.total_score() 

177 severity = self.severity(req) 

178 answer_dict = {None: None} 

179 for option in range(0, 4): 

180 answer_dict[option] = ( 

181 str(option) + " — " + self.wxstring(req, "a" + str(option)) 

182 ) 

183 

184 q_a = "" 

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

186 q_a += tr_qa( 

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

188 get_from_dict(answer_dict, getattr(self, "q" + str(q))), 

189 ) 

190 

191 return """ 

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

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

194 {tr_is_complete} 

195 {total_score} 

196 {anxiety_severity} 

197 </table> 

198 </div> 

199 <div class="{CssClass.EXPLANATION}"> 

200 Ratings are over the last 2 weeks. 

201 </div> 

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

203 <tr> 

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

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

206 </tr> 

207 {q_a} 

208 </table> 

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

210 [1] ≥15 severe, ≥10 moderate, ≥5 mild. 

211 Score ≥10 identifies: generalized anxiety disorder with 

212 sensitivity 89%, specificity 82% (Spitzer et al. 2006, PubMed 

213 ID 16717171); 

214 panic disorder with sensitivity 74%, specificity 81% (Kroenke 

215 et al. 2010, PMID 20633738); 

216 social anxiety with sensitivity 72%, specificity 80% (Kroenke 

217 et al. 2010); 

218 post-traumatic stress disorder with sensitivity 66%, 

219 specificity 81% (Kroenke et al. 2010). 

220 The majority of evidence contributing to these figures comes 

221 from primary care screening studies. 

222 </div> 

223 """.format( 

224 CssClass=CssClass, 

225 tr_is_complete=self.get_is_complete_tr(req), 

226 total_score=tr( 

227 req.sstring(SS.TOTAL_SCORE), 

228 answer(score) + " / {}".format(self.MAX_SCORE), 

229 ), 

230 anxiety_severity=tr( 

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

232 severity, 

233 ), 

234 q_a=q_a, 

235 ) 

236 

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

238 codes = [ 

239 SnomedExpression( 

240 req.snomed(SnomedLookup.GAD7_PROCEDURE_ASSESSMENT) 

241 ) 

242 ] 

243 if self.is_complete(): 

244 codes.append( 

245 SnomedExpression( 

246 req.snomed(SnomedLookup.GAD7_SCALE), 

247 {req.snomed(SnomedLookup.GAD7_SCORE): self.total_score()}, 

248 ) 

249 ) 

250 return codes