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/hama.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_sqla_coltypes import SummaryCategoryColType 

41from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

42from camcops_server.cc_modules.cc_task import ( 

43 get_from_dict, 

44 Task, 

45 TaskHasClinicianMixin, 

46 TaskHasPatientMixin, 

47) 

48from camcops_server.cc_modules.cc_text import SS 

49from camcops_server.cc_modules.cc_trackerhelpers import ( 

50 TrackerInfo, 

51 TrackerLabel, 

52) 

53 

54 

55# ============================================================================= 

56# HAM-A 

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

58 

59class HamaMetaclass(DeclarativeMeta): 

60 # noinspection PyInitNewSignature 

61 def __init__(cls: Type['Hama'], 

62 name: str, 

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

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

65 add_multiple_columns( 

66 cls, "q", 1, cls.NQUESTIONS, 

67 comment_fmt="Q{n}, {s} (0-4, higher worse)", 

68 minimum=0, maximum=4, 

69 comment_strings=[ 

70 "anxious mood", "tension", "fears", "insomnia", 

71 "concentration/memory", "depressed mood", "somatic, muscular", 

72 "somatic, sensory", "cardiovascular", "respiratory", 

73 "gastrointestinal", "genitourinary", "other autonomic", 

74 "behaviour in interview" 

75 ] 

76 ) 

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

78 

79 

80class Hama(TaskHasPatientMixin, TaskHasClinicianMixin, Task, 

81 metaclass=HamaMetaclass): 

82 """ 

83 Server implementation of the HAM-A task. 

84 """ 

85 __tablename__ = "hama" 

86 shortname = "HAM-A" 

87 provides_trackers = True 

88 

89 NQUESTIONS = 14 

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

91 MAX_SCORE = 56 

92 

93 @staticmethod 

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

95 _ = req.gettext 

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

97 

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

99 return [TrackerInfo( 

100 value=self.total_score(), 

101 plot_label="HAM-A total score", 

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

103 axis_min=-0.5, 

104 axis_max=self.MAX_SCORE + 0.5, 

105 horizontal_lines=[30.5, 24.5, 17.5], 

106 horizontal_labels=[ 

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

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

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

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

111 ] 

112 )] 

113 

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

115 if not self.is_complete(): 

116 return CTV_INCOMPLETE 

117 return [CtvInfo(content=( 

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

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

120 ))] 

121 

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

123 return self.standard_task_summary_fields() + [ 

124 SummaryElement(name="total", 

125 coltype=Integer(), 

126 value=self.total_score(), 

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

128 SummaryElement(name="severity", 

129 coltype=SummaryCategoryColType, 

130 value=self.severity(req), 

131 comment="Severity"), 

132 ] 

133 

134 def is_complete(self) -> bool: 

135 return ( 

136 self.all_fields_not_none(self.TASK_FIELDS) and 

137 self.field_contents_valid() 

138 ) 

139 

140 def total_score(self) -> int: 

141 return self.sum_fields(self.TASK_FIELDS) 

142 

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

144 score = self.total_score() 

145 if score >= 31: 

146 return req.sstring(SS.VERY_SEVERE) 

147 elif score >= 25: 

148 return req.sstring(SS.MODERATE_TO_SEVERE) 

149 elif score >= 18: 

150 return req.sstring(SS.MILD_TO_MODERATE) 

151 else: 

152 return req.sstring(SS.MILD) 

153 

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

155 score = self.total_score() 

156 severity = self.severity(req) 

157 answer_dicts = [] 

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

159 d = {None: None} 

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

161 d[option] = self.wxstring(req, "q" + str(q) + "_option" + 

162 str(option)) 

163 answer_dicts.append(d) 

164 q_a = "" 

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

166 q_a += tr_qa( 

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

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

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

170 ) 

171 return """ 

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

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

174 {tr_is_complete} 

175 {total_score} 

176 {symptom_severity} 

177 </table> 

178 </div> 

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

180 <tr> 

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

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

183 </tr> 

184 {q_a} 

185 </table> 

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

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

188 ≥18 mild to moderate, otherwise mild. 

189 </div> 

190 """.format( 

191 CssClass=CssClass, 

192 tr_is_complete=self.get_is_complete_tr(req), 

193 total_score=tr( 

194 req.sstring(SS.TOTAL_SCORE), 

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

196 ), 

197 symptom_severity=tr_qa( 

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

199 severity 

200 ), 

201 q_a=q_a, 

202 )