Coverage for tasks/epds.py: 60%

58 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/epds.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**EPDS task.** 

29 

30""" 

31 

32from typing import Any, Dict, List, Tuple, Type 

33 

34from cardinal_pythonlib.stringfunc import strseq 

35from sqlalchemy.ext.declarative import DeclarativeMeta 

36from sqlalchemy.sql.sqltypes import Integer 

37 

38from camcops_server.cc_modules.cc_constants import CssClass 

39from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

40from camcops_server.cc_modules.cc_db import add_multiple_columns 

41from camcops_server.cc_modules.cc_html import get_yes_no, tr_qa 

42from camcops_server.cc_modules.cc_request import CamcopsRequest 

43from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

44from camcops_server.cc_modules.cc_task import ( 

45 get_from_dict, 

46 Task, 

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# EPDS 

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

59 

60 

61class EpdsMetaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__( 

64 cls: Type["Epds"], 

65 name: str, 

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

67 classdict: Dict[str, Any], 

68 ) -> None: 

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

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

71 

72 

73class Epds(TaskHasPatientMixin, Task, metaclass=EpdsMetaclass): 

74 __tablename__ = "epds" 

75 shortname = "EPDS" 

76 provides_trackers = True 

77 

78 NQUESTIONS = 10 

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

80 MAX_TOTAL = 30 

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

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

83 

84 @staticmethod 

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

86 _ = req.gettext 

87 return _("Edinburgh Postnatal Depression Scale") 

88 

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

90 return [ 

91 TrackerInfo( 

92 value=self.total_score(), 

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

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

95 axis_min=-0.5, 

96 axis_max=self.MAX_TOTAL + 0.5, 

97 horizontal_lines=[ 

98 self.CUTOFF_2_GREATER_OR_EQUAL - 0.5, 

99 self.CUTOFF_1_GREATER_OR_EQUAL - 0.5, 

100 ], 

101 horizontal_labels=[ 

102 TrackerLabel( 

103 self.CUTOFF_2_GREATER_OR_EQUAL, "likely depression" 

104 ), 

105 TrackerLabel( 

106 self.CUTOFF_1_GREATER_OR_EQUAL, "possible depression" 

107 ), 

108 ], 

109 ) 

110 ] 

111 

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

113 if not self.is_complete(): 

114 return CTV_INCOMPLETE 

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

116 return [CtvInfo(content=text)] 

117 

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

119 return self.standard_task_summary_fields() + [ 

120 SummaryElement( 

121 name="total", 

122 coltype=Integer(), 

123 value=self.total_score(), 

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

125 ) 

126 ] 

127 

128 def is_complete(self) -> bool: 

129 return self.all_fields_not_none(self.TASK_FIELDS) 

130 

131 def total_score(self) -> int: 

132 return self.sum_fields(self.TASK_FIELDS) 

133 

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

135 score = self.total_score() 

136 above_cutoff_1 = score >= 10 

137 above_cutoff_2 = score >= 13 

138 answer_dicts = [] 

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

140 d = {None: "?"} 

141 for option in range(0, 4): 

142 d[option] = ( 

143 str(option) 

144 + " — " 

145 + self.wxstring( 

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

147 ) 

148 ) 

149 answer_dicts.append(d) 

150 

151 q_a = "" 

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

153 q_a += tr_qa( 

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

155 get_from_dict( 

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

157 ), 

158 ) 

159 

160 return f""" 

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

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

163 {self.get_is_complete_tr(req)} 

164 <tr> 

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

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

167 </tr> 

168 <tr> 

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

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

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

172 </tr> 

173 <tr> 

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

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

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

177 </tr> 

178 </table> 

179 </div> 

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

181 Ratings are over the last week. 

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

183 </div> 

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

185 <tr> 

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

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

188 </tr> 

189 {q_a} 

190 </table> 

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

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

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

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

195 </div> 

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

197 Edinburgh Postnatal Depression Scale: 

198 © 1987 The Royal College of Psychiatrists. The Edinburgh 

199 Postnatal Depression Scale may be photocopied by individual 

200 researchers or clinicians for their own use without seeking 

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

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

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

204 postnatal depression. Development of the 10-item Edinburgh 

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

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

207 College of Psychiatrists for copying and distribution to others 

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

209 </div> 

210 """