Coverage for tasks/hama.py: 55%

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

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_sqla_coltypes import SummaryCategoryColType 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import ( 

44 get_from_dict, 

45 Task, 

46 TaskHasClinicianMixin, 

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# HAM-A 

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

59 

60 

61class HamaMetaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__( 

64 cls: Type["Hama"], 

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 comment_fmt="Q{n}, {s} (0-4, higher worse)", 

75 minimum=0, 

76 maximum=4, 

77 comment_strings=[ 

78 "anxious mood", 

79 "tension", 

80 "fears", 

81 "insomnia", 

82 "concentration/memory", 

83 "depressed mood", 

84 "somatic, muscular", 

85 "somatic, sensory", 

86 "cardiovascular", 

87 "respiratory", 

88 "gastrointestinal", 

89 "genitourinary", 

90 "other autonomic", 

91 "behaviour in interview", 

92 ], 

93 ) 

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

95 

96 

97class Hama( 

98 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=HamaMetaclass 

99): 

100 """ 

101 Server implementation of the HAM-A task. 

102 """ 

103 

104 __tablename__ = "hama" 

105 shortname = "HAM-A" 

106 provides_trackers = True 

107 

108 NQUESTIONS = 14 

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

110 MAX_SCORE = 56 

111 

112 @staticmethod 

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

114 _ = req.gettext 

115 return _("Hamilton Rating Scale for Anxiety") 

116 

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

118 return [ 

119 TrackerInfo( 

120 value=self.total_score(), 

121 plot_label="HAM-A total score", 

122 axis_label=f"Total score (out of {self.MAX_SCORE})", 

123 axis_min=-0.5, 

124 axis_max=self.MAX_SCORE + 0.5, 

125 horizontal_lines=[30.5, 24.5, 17.5], 

126 horizontal_labels=[ 

127 TrackerLabel(33, req.sstring(SS.VERY_SEVERE)), 

128 TrackerLabel(27.5, req.sstring(SS.MODERATE_TO_SEVERE)), 

129 TrackerLabel(21, req.sstring(SS.MILD_TO_MODERATE)), 

130 TrackerLabel(8.75, req.sstring(SS.MILD)), 

131 ], 

132 ) 

133 ] 

134 

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

136 if not self.is_complete(): 

137 return CTV_INCOMPLETE 

138 return [ 

139 CtvInfo( 

140 content=( 

141 f"HAM-A total score {self.total_score()}/{self.MAX_SCORE} " 

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

143 ) 

144 ) 

145 ] 

146 

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

148 return self.standard_task_summary_fields() + [ 

149 SummaryElement( 

150 name="total", 

151 coltype=Integer(), 

152 value=self.total_score(), 

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

154 ), 

155 SummaryElement( 

156 name="severity", 

157 coltype=SummaryCategoryColType, 

158 value=self.severity(req), 

159 comment="Severity", 

160 ), 

161 ] 

162 

163 def is_complete(self) -> bool: 

164 return ( 

165 self.all_fields_not_none(self.TASK_FIELDS) 

166 and self.field_contents_valid() 

167 ) 

168 

169 def total_score(self) -> int: 

170 return self.sum_fields(self.TASK_FIELDS) 

171 

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

173 score = self.total_score() 

174 if score >= 31: 

175 return req.sstring(SS.VERY_SEVERE) 

176 elif score >= 25: 

177 return req.sstring(SS.MODERATE_TO_SEVERE) 

178 elif score >= 18: 

179 return req.sstring(SS.MILD_TO_MODERATE) 

180 else: 

181 return req.sstring(SS.MILD) 

182 

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

184 score = self.total_score() 

185 severity = self.severity(req) 

186 answer_dicts = [] 

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

188 d = {None: None} 

189 for option in range(0, 4 + 1): 

190 d[option] = self.wxstring( 

191 req, "q" + str(q) + "_option" + str(option) 

192 ) 

193 answer_dicts.append(d) 

194 q_a = "" 

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

196 q_a += tr_qa( 

197 self.wxstring(req, "q" + str(q) + "_s") 

198 + " " 

199 + self.wxstring(req, "q" + str(q) + "_question"), 

200 get_from_dict( 

201 answer_dicts[q - 1], getattr(self, "q" + str(q)) 

202 ), 

203 ) 

204 return """ 

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

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

207 {tr_is_complete} 

208 {total_score} 

209 {symptom_severity} 

210 </table> 

211 </div> 

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

213 <tr> 

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

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

216 </tr> 

217 {q_a} 

218 </table> 

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

220 [1] ≥31 very severe, ≥25 moderate to severe, 

221 ≥18 mild to moderate, otherwise mild. 

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 symptom_severity=tr_qa( 

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

232 severity, 

233 ), 

234 q_a=q_a, 

235 )