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/hamd7.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 answer, tr, tr_qa 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_sqla_coltypes import ( 

41 SummaryCategoryColType, 

42 ZERO_TO_TWO_CHECKER, 

43) 

44from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

45from camcops_server.cc_modules.cc_task import ( 

46 get_from_dict, 

47 Task, 

48 TaskHasClinicianMixin, 

49 TaskHasPatientMixin, 

50) 

51from camcops_server.cc_modules.cc_text import SS 

52from camcops_server.cc_modules.cc_trackerhelpers import ( 

53 TrackerInfo, 

54 TrackerLabel, 

55) 

56 

57 

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

59# HAMD-7 

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

61 

62class Hamd7Metaclass(DeclarativeMeta): 

63 # noinspection PyInitNewSignature,PyUnresolvedReferences 

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

65 name: str, 

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

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

68 add_multiple_columns( 

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

70 minimum=0, maximum=4, # see below 

71 comment_fmt="Q{n}, {s} (0-4, except Q6 0-2; higher worse)", 

72 comment_strings=["depressed mood", "guilt", 

73 "interest/pleasure/level of activities", 

74 "psychological anxiety", "somatic anxiety", 

75 "energy/somatic symptoms", "suicide"] 

76 ) 

77 # Now fix the wrong bits. Hardly elegant! 

78 cls.q6.set_permitted_value_checker(ZERO_TO_TWO_CHECKER) 

79 

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

81 

82 

83class Hamd7(TaskHasPatientMixin, TaskHasClinicianMixin, Task, 

84 metaclass=Hamd7Metaclass): 

85 """ 

86 Server implementation of the HAMD-7 task. 

87 """ 

88 __tablename__ = "hamd7" 

89 shortname = "HAMD-7" 

90 provides_trackers = True 

91 

92 NQUESTIONS = 7 

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

94 MAX_SCORE = 26 

95 

96 @staticmethod 

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

98 _ = req.gettext 

99 return _("Hamilton Rating Scale for Depression (7-item scale)") 

100 

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

102 return [TrackerInfo( 

103 value=self.total_score(), 

104 plot_label="HAM-D-7 total score", 

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

106 axis_min=-0.5, 

107 axis_max=self.MAX_SCORE + 0.5, 

108 horizontal_lines=[19.5, 11.5, 3.5], 

109 horizontal_labels=[ 

110 TrackerLabel(23, self.wxstring(req, "severity_severe")), 

111 TrackerLabel(15.5, self.wxstring(req, "severity_moderate")), 

112 TrackerLabel(7.5, self.wxstring(req, "severity_mild")), 

113 TrackerLabel(1.75, self.wxstring(req, "severity_none")), 

114 ] 

115 )] 

116 

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

118 if not self.is_complete(): 

119 return CTV_INCOMPLETE 

120 return [CtvInfo(content=( 

121 f"HAM-D-7 total score {self.total_score()}/{self.MAX_SCORE} " 

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

123 ))] 

124 

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

126 return self.standard_task_summary_fields() + [ 

127 SummaryElement(name="total", 

128 coltype=Integer(), 

129 value=self.total_score(), 

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

131 SummaryElement(name="severity", 

132 coltype=SummaryCategoryColType, 

133 value=self.severity(req), 

134 comment="Severity"), 

135 ] 

136 

137 def is_complete(self) -> bool: 

138 return ( 

139 self.all_fields_not_none(self.TASK_FIELDS) and 

140 self.field_contents_valid() 

141 ) 

142 

143 def total_score(self) -> int: 

144 return self.sum_fields(self.TASK_FIELDS) 

145 

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

147 score = self.total_score() 

148 if score >= 20: 

149 return self.wxstring(req, "severity_severe") 

150 elif score >= 12: 

151 return self.wxstring(req, "severity_moderate") 

152 elif score >= 4: 

153 return self.wxstring(req, "severity_mild") 

154 else: 

155 return self.wxstring(req, "severity_none") 

156 

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

158 score = self.total_score() 

159 severity = self.severity(req) 

160 answer_dicts = [] 

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

162 d = {None: None} 

163 for option in range(0, 5): 

164 if q == 6 and option > 2: 

165 continue 

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

167 str(option)) 

168 answer_dicts.append(d) 

169 

170 q_a = "" 

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

172 q_a += tr_qa( 

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

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

175 ) 

176 

177 return """ 

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

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

180 {tr_is_complete} 

181 {total_score} 

182 {severity} 

183 </table> 

184 </div> 

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

186 <tr> 

187 <th width="30%">Question</th> 

188 <th width="70%">Answer</th> 

189 </tr> 

190 {q_a} 

191 </table> 

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

193 [1] ≥20 severe, ≥12 moderate, ≥4 mild, &lt;4 none. 

194 </div> 

195 """.format( 

196 CssClass=CssClass, 

197 tr_is_complete=self.get_is_complete_tr(req), 

198 total_score=tr( 

199 req.sstring(SS.TOTAL_SCORE), 

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

201 ), 

202 severity=tr_qa( 

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

204 severity 

205 ), 

206 q_a=q_a, 

207 )