Coverage for tasks/epds.py: 58%

55 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/epds.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**EPDS task.** 

27 

28""" 

29 

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

31 

32from cardinal_pythonlib.stringfunc import strseq 

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 get_yes_no, tr_qa 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

41from camcops_server.cc_modules.cc_task import ( 

42 get_from_dict, 

43 Task, 

44 TaskHasPatientMixin, 

45) 

46from camcops_server.cc_modules.cc_text import SS 

47from camcops_server.cc_modules.cc_trackerhelpers import ( 

48 TrackerInfo, 

49 TrackerLabel, 

50) 

51 

52 

53# ============================================================================= 

54# EPDS 

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

56 

57 

58class Epds( # type: ignore[misc] 

59 TaskHasPatientMixin, 

60 Task, 

61): 

62 __tablename__ = "epds" 

63 shortname = "EPDS" 

64 provides_trackers = True 

65 

66 NQUESTIONS = 10 

67 

68 @classmethod 

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

70 add_multiple_columns(cls, "q", 1, cls.NQUESTIONS) 

71 

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

73 MAX_TOTAL = 30 

74 CUTOFF_1_GREATER_OR_EQUAL = 10 # Cox et al. 1987, PubMed ID 3651732. 

75 CUTOFF_2_GREATER_OR_EQUAL = 13 # Cox et al. 1987, PubMed ID 3651732. 

76 

77 @staticmethod 

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

79 _ = req.gettext 

80 return _("Edinburgh Postnatal Depression Scale") 

81 

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

83 return [ 

84 TrackerInfo( 

85 value=self.total_score(), 

86 plot_label="EPDS total score (rating depressive symptoms)", 

87 axis_label=f"Total score (out of {self.MAX_TOTAL})", 

88 axis_min=-0.5, 

89 axis_max=self.MAX_TOTAL + 0.5, 

90 horizontal_lines=[ 

91 self.CUTOFF_2_GREATER_OR_EQUAL - 0.5, 

92 self.CUTOFF_1_GREATER_OR_EQUAL - 0.5, 

93 ], 

94 horizontal_labels=[ 

95 TrackerLabel( 

96 self.CUTOFF_2_GREATER_OR_EQUAL, "likely depression" 

97 ), 

98 TrackerLabel( 

99 self.CUTOFF_1_GREATER_OR_EQUAL, "possible depression" 

100 ), 

101 ], 

102 ) 

103 ] 

104 

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

106 if not self.is_complete(): 

107 return CTV_INCOMPLETE 

108 text = f"EPDS total: {self.total_score()}/{self.MAX_TOTAL}" 

109 return [CtvInfo(content=text)] 

110 

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

112 return self.standard_task_summary_fields() + [ 

113 SummaryElement( 

114 name="total", 

115 coltype=Integer(), 

116 value=self.total_score(), 

117 comment=f"Total score (out of {self.MAX_TOTAL})", 

118 ) 

119 ] 

120 

121 def is_complete(self) -> bool: 

122 return self.all_fields_not_none(self.TASK_FIELDS) 

123 

124 def total_score(self) -> int: 

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

126 

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

128 score = self.total_score() 

129 above_cutoff_1 = score >= 10 

130 above_cutoff_2 = score >= 13 

131 answer_dicts = [] 

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

133 d: dict[Optional[int], Optional[str]] = {None: "?"} 

134 for option in range(0, 4): 

135 d[option] = ( 

136 str(option) 

137 + " — " 

138 + self.wxstring( 

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

140 ) 

141 ) 

142 answer_dicts.append(d) 

143 

144 q_a = "" 

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

146 q_a += tr_qa( 

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

148 get_from_dict( 

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

150 ), 

151 ) 

152 

153 return f""" 

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

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

156 {self.get_is_complete_tr(req)} 

157 <tr> 

158 <td>{req.sstring(SS.TOTAL_SCORE)}</td> 

159 <td><b>{score}</b> / {self.MAX_TOTAL}</td> 

160 </tr> 

161 <tr> 

162 <td>{self.wxstring(req, "above_cutoff_1")} 

163 <sup>[1]</sup></td> 

164 <td><b>{get_yes_no(req, above_cutoff_1)}</b></td> 

165 </tr> 

166 <tr> 

167 <td>{self.wxstring(req, "above_cutoff_2")} 

168 <sup>[2]</sup></td> 

169 <td><b>{get_yes_no(req, above_cutoff_2)}</b></td> 

170 </tr> 

171 </table> 

172 </div> 

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

174 Ratings are over the last week. 

175 <b>{self.wxstring(req, "always_look_at_suicide")}</b> 

176 </div> 

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

178 <tr> 

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

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

181 </tr> 

182 {q_a} 

183 </table> 

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

185 [1] &ge;{self.CUTOFF_1_GREATER_OR_EQUAL}. 

186 [2] &ge;{self.CUTOFF_2_GREATER_OR_EQUAL}. 

187 (Cox et al. 1987, PubMed ID 3651732.) 

188 </div> 

189 <div class="{CssClass.COPYRIGHT}"> 

190 Edinburgh Postnatal Depression Scale: 

191 © 1987 The Royal College of Psychiatrists. The Edinburgh 

192 Postnatal Depression Scale may be photocopied by individual 

193 researchers or clinicians for their own use without seeking 

194 permission from the publishers. The scale must be copied in 

195 full and all copies must acknowledge the following source: Cox, 

196 J.L., Holden, J.M., & Sagovsky, R. (1987). Detection of 

197 postnatal depression. Development of the 10-item Edinburgh 

198 Postnatal Depression Scale. British Journal of Psychiatry, 150, 

199 782-786. Written permission must be obtained from the Royal 

200 College of Psychiatrists for copying and distribution to others 

201 or for republication (in print, online or by any other medium). 

202 </div> 

203 """