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

28 

29""" 

30 

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

32 

33from cardinal_pythonlib.stringfunc import strseq 

34from sqlalchemy.ext.declarative import DeclarativeMeta 

35from sqlalchemy.sql.sqltypes import Integer 

36 

37from camcops_server.cc_modules.cc_constants import CssClass 

38from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

39from camcops_server.cc_modules.cc_db import add_multiple_columns 

40from camcops_server.cc_modules.cc_html import get_yes_no, tr_qa 

41from camcops_server.cc_modules.cc_request import CamcopsRequest 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import ( 

44 get_from_dict, 

45 Task, 

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

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

58 

59class EpdsMetaclass(DeclarativeMeta): 

60 # noinspection PyInitNewSignature 

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

62 name: str, 

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

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

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

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

67 

68 

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

70 __tablename__ = "epds" 

71 shortname = "EPDS" 

72 provides_trackers = True 

73 

74 NQUESTIONS = 10 

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

76 MAX_TOTAL = 30 

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

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

79 

80 @staticmethod 

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

82 _ = req.gettext 

83 return _("Edinburgh Postnatal Depression Scale") 

84 

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

86 return [TrackerInfo( 

87 value=self.total_score(), 

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

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

90 axis_min=-0.5, 

91 axis_max=self.MAX_TOTAL + 0.5, 

92 horizontal_lines=[ 

93 self.CUTOFF_2_GREATER_OR_EQUAL - 0.5, 

94 self.CUTOFF_1_GREATER_OR_EQUAL - 0.5, 

95 ], 

96 horizontal_labels=[ 

97 TrackerLabel(self.CUTOFF_2_GREATER_OR_EQUAL, 

98 "likely depression"), 

99 TrackerLabel(self.CUTOFF_1_GREATER_OR_EQUAL, 

100 "possible depression"), 

101 ] 

102 )] 

103 

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

105 if not self.is_complete(): 

106 return CTV_INCOMPLETE 

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

108 return [CtvInfo(content=text)] 

109 

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

111 return self.standard_task_summary_fields() + [ 

112 SummaryElement( 

113 name="total", coltype=Integer(), 

114 value=self.total_score(), 

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

116 ), 

117 ] 

118 

119 def is_complete(self) -> bool: 

120 return self.all_fields_not_none(self.TASK_FIELDS) 

121 

122 def total_score(self) -> int: 

123 return self.sum_fields(self.TASK_FIELDS) 

124 

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

126 score = self.total_score() 

127 above_cutoff_1 = score >= 10 

128 above_cutoff_2 = score >= 13 

129 answer_dicts = [] 

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

131 d = {None: "?"} 

132 for option in range(0, 4): 

133 d[option] = ( 

134 str(option) + " — " + 

135 self.wxstring(req, "q" + str(q) + "_option" + str(option))) 

136 answer_dicts.append(d) 

137 

138 q_a = "" 

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

140 q_a += tr_qa( 

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

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

143 ) 

144 

145 return f""" 

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

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

148 {self.get_is_complete_tr(req)} 

149 <tr> 

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

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

152 </tr> 

153 <tr> 

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

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

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

157 </tr> 

158 <tr> 

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

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

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

162 </tr> 

163 </table> 

164 </div> 

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

166 Ratings are over the last week. 

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

168 </div> 

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

170 <tr> 

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

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

173 </tr> 

174 {q_a} 

175 </table> 

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

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

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

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

180 </div> 

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

182 Edinburgh Postnatal Depression Scale: 

183 © 1987 The Royal College of Psychiatrists. The Edinburgh 

184 Postnatal Depression Scale may be photocopied by individual 

185 researchers or clinicians for their own use without seeking 

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

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

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

189 postnatal depression. Development of the 10-item Edinburgh 

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

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

192 College of Psychiatrists for copying and distribution to others 

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

194 </div> 

195 """