Coverage for tasks/gds.py: 59%

64 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/gds.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""" 

29 

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

31 

32from cardinal_pythonlib.stringfunc import strseq 

33from sqlalchemy.ext.declarative import DeclarativeMeta 

34from sqlalchemy.sql.sqltypes import Integer, String 

35 

36from camcops_server.cc_modules.cc_constants import CssClass, NO_CHAR, YES_CHAR 

37from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

38from camcops_server.cc_modules.cc_db import add_multiple_columns 

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

40from camcops_server.cc_modules.cc_request import CamcopsRequest 

41from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

44from camcops_server.cc_modules.cc_text import SS 

45from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

46 

47 

48# ============================================================================= 

49# GDS-15 

50# ============================================================================= 

51 

52 

53class Gds15Metaclass(DeclarativeMeta): 

54 # noinspection PyInitNewSignature 

55 def __init__( 

56 cls: Type["Gds15"], 

57 name: str, 

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

59 classdict: Dict[str, Any], 

60 ) -> None: 

61 add_multiple_columns( 

62 cls, 

63 "q", 

64 1, 

65 cls.NQUESTIONS, 

66 String(length=1), 

67 pv=[NO_CHAR, YES_CHAR], 

68 comment_fmt="Q{n}, {s} ('Y' or 'N')", 

69 comment_strings=[ 

70 "satisfied", 

71 "dropped activities", 

72 "life empty", 

73 "bored", 

74 "good spirits", # 5 

75 "afraid", 

76 "happy", 

77 "helpless", 

78 "stay at home", 

79 "memory problems", # 10 

80 "wonderful to be alive", 

81 "worthless", 

82 "full of energy", 

83 "hopeless", 

84 "others better off", # 15 

85 ], 

86 ) 

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

88 

89 

90class Gds15(TaskHasPatientMixin, Task, metaclass=Gds15Metaclass): 

91 """ 

92 Server implementation of the GDS-15 task. 

93 """ 

94 

95 __tablename__ = "gds15" 

96 shortname = "GDS-15" 

97 info_filename_stem = "gds" 

98 provides_trackers = True 

99 

100 NQUESTIONS = 15 

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

102 SCORE_IF_YES = [2, 3, 4, 6, 8, 9, 10, 12, 14, 15] 

103 SCORE_IF_NO = [1, 5, 7, 11, 13] 

104 MAX_SCORE = 15 

105 

106 @staticmethod 

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

108 _ = req.gettext 

109 return _("Geriatric Depression Scale, 15-item version") 

110 

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

112 return [ 

113 TrackerInfo( 

114 value=self.total_score(), 

115 plot_label="GDS-15 total score", 

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

117 axis_min=-0.5, 

118 axis_max=self.MAX_SCORE + 0.5, 

119 ) 

120 ] 

121 

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

123 if not self.is_complete(): 

124 return CTV_INCOMPLETE 

125 return [ 

126 CtvInfo( 

127 content=f"GDS-15 total score " 

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

129 ) 

130 ] 

131 

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

133 return self.standard_task_summary_fields() + [ 

134 SummaryElement( 

135 name="total", 

136 coltype=Integer(), 

137 value=self.total_score(), 

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

139 ) 

140 ] 

141 

142 def is_complete(self) -> bool: 

143 return ( 

144 self.all_fields_not_none(self.TASK_FIELDS) 

145 and self.field_contents_valid() 

146 ) 

147 

148 def total_score(self) -> int: 

149 score = 0 

150 for q in self.SCORE_IF_YES: 

151 if getattr(self, "q" + str(q)) == YES_CHAR: 

152 score += 1 

153 for q in self.SCORE_IF_NO: 

154 if getattr(self, "q" + str(q)) == NO_CHAR: 

155 score += 1 

156 return score 

157 

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

159 score = self.total_score() 

160 

161 q_a = "" 

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

163 suffix = " †" if q in self.SCORE_IF_YES else " *" 

164 q_a += tr_qa( 

165 str(q) + ". " + self.wxstring(req, "q" + str(q)) + suffix, 

166 getattr(self, "q" + str(q)), 

167 ) 

168 

169 return f""" 

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

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

172 {self.get_is_complete_tr(req)} 

173 {tr(req.sstring(SS.TOTAL_SCORE), 

174 answer(score) + f" / {self.MAX_SCORE}")} 

175 </table> 

176 </div> 

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

178 Ratings are over the last 1 week. 

179 </div> 

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

181 <tr> 

182 <th width="70%">Question</th> 

183 <th width="30%">Answer</th> 

184 </tr> 

185 {q_a} 

186 </table> 

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

188 (†) ‘Y’ scores 1; ‘N’ scores 0. 

189 (*) ‘Y’ scores 0; ‘N’ scores 1. 

190 </div> 

191 """ 

192 

193 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]: 

194 codes = [ 

195 SnomedExpression( 

196 req.snomed(SnomedLookup.GDS15_PROCEDURE_ASSESSMENT) 

197 ) 

198 ] 

199 if self.is_complete(): 

200 codes.append( 

201 SnomedExpression( 

202 req.snomed(SnomedLookup.GDS15_SCALE), 

203 {req.snomed(SnomedLookup.GDS15_SCORE): self.total_score()}, 

204 ) 

205 ) 

206 return codes