Coverage for tasks/hamd7.py: 53%

1"""

2 camcops_server/tasks/hamd7.py

3

4===============================================================================

5

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

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

8

9 This file is part of CamCOPS.

10

11 CamCOPS is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License as published by

13 the Free Software Foundation, either version 3 of the License, or

14 (at your option) any later version.

15

16 CamCOPS is distributed in the hope that it will be useful,

17 but WITHOUT ANY WARRANTY; without even the implied warranty of

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

19 GNU General Public License for more details.

20

21 You should have received a copy of the GNU General Public License

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

23

24===============================================================================

25

26"""

27

28 from typing import Any, cast, List, Optional, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31 from sqlalchemy.sql.sqltypes import Integer

32

33 from camcops_server.cc_modules.cc_constants import CssClass

34 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

35 from camcops_server.cc_modules.cc_db import add_multiple_columns

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

37 from camcops_server.cc_modules.cc_request import CamcopsRequest

38 from camcops_server.cc_modules.cc_sqla_coltypes import (

39 SummaryCategoryColType,

40)

41 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

42 from camcops_server.cc_modules.cc_task import (

43 get_from_dict,

44 Task,

45 TaskHasClinicianMixin,

46 TaskHasPatientMixin,

47)

48 from camcops_server.cc_modules.cc_text import SS

49 from camcops_server.cc_modules.cc_trackerhelpers import (

50 TrackerInfo,

51 TrackerLabel,

52)

53

54

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

56# HAMD-7

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

58

59

60 class Hamd7( # type: ignore[misc]

61 TaskHasPatientMixin,

62 TaskHasClinicianMixin,

63 Task,

64):

65 """

66 Server implementation of the HAMD-7 task.

67 """

68

69 __tablename__ = "hamd7"

70 shortname = "HAMD-7"

71 info_filename_stem = "hamd"

72 provides_trackers = True

73

74 NQUESTIONS = 7

75

76 @classmethod

77 def extend_columns(cls: Type["Hamd7"], **kwargs: Any) -> None:

78 add_multiple_columns(

79 cls,

80 "q",

81 1,

82 5,

83 minimum=0,

84 maximum=4,

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

86 comment_strings=[

87 "depressed mood",

88 "guilt",

89 "interest/pleasure/level of activities",

90 "psychological anxiety",

91 "somatic anxiety",

92 ],

93 )

94 add_multiple_columns(

95 cls,

96 "q",

97 6,

98 6,

99 minimum=0,

100 maximum=2,

101 comment_fmt="Q{n}, {s} (0-2, higher worse)",

102 comment_strings=[

103 "energy/somatic symptoms",

104 ],

105 )

106 add_multiple_columns(

107 cls,

108 "q",

109 7,

110 7,

111 minimum=0,

112 maximum=4,

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

114 comment_strings=[

115 "suicide",

116 ],

117 )

118

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

120 MAX_SCORE = 26

121

122 @staticmethod

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

124 _ = req.gettext

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

126

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

128 return [

129 TrackerInfo(

130 value=self.total_score(),

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

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

133 axis_min=-0.5,

134 axis_max=self.MAX_SCORE + 0.5,

135 horizontal_lines=[19.5, 11.5, 3.5],

136 horizontal_labels=[

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

138 TrackerLabel(

139 15.5, self.wxstring(req, "severity_moderate")

140 ),

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

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

143 ],

144 )

145 ]

146

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

148 if not self.is_complete():

149 return CTV_INCOMPLETE

150 return [

151 CtvInfo(

152 content=(

153 f"HAM-D-7 total score "

154 f"{self.total_score()}/{self.MAX_SCORE} "

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

156 )

157 )

158 ]

159

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

161 return self.standard_task_summary_fields() + [

162 SummaryElement(

163 name="total",

164 coltype=Integer(),

165 value=self.total_score(),

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

167 ),

168 SummaryElement(

169 name="severity",

170 coltype=SummaryCategoryColType,

171 value=self.severity(req),

172 comment="Severity",

173 ),

174 ]

175

176 def is_complete(self) -> bool:

177 return (

178 self.all_fields_not_none(self.TASK_FIELDS)

179 and self.field_contents_valid()

180 )

181

182 def total_score(self) -> int:

183 return cast(int, self.sum_fields(self.TASK_FIELDS))

184

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

186 score = self.total_score()

187 if score >= 20:

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

189 elif score >= 12:

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

191 elif score >= 4:

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

193 else:

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

195

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

197 score = self.total_score()

198 severity = self.severity(req)

199 answer_dicts = []

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

201 d: dict[Optional[int], Optional[str]] = {None: None}

202 for option in range(0, 5):

203 if q == 6 and option > 2:

204 continue

205 d[option] = self.wxstring(

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

207 )

208 answer_dicts.append(d)

209

210 q_a = ""

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

212 q_a += tr_qa(

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

214 get_from_dict(

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

216 ),

217 )

218

219 return """

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

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

222 {tr_is_complete}

223 {total_score}

224 {severity}

225 </table>

226 </div>

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

228 <tr>

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

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

231 </tr>

232 {q_a}

233 </table>

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

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

236 </div>

237 """.format(

238 CssClass=CssClass,

239 tr_is_complete=self.get_is_complete_tr(req),

240 total_score=tr(

241 req.sstring(SS.TOTAL_SCORE),

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

243 ),

244 severity=tr_qa(

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

246 ),

247 q_a=q_a,

248 )