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

28 

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

30 

31from cardinal_pythonlib.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.sqltypes import Integer, String 

34 

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

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 answer, tr, tr_qa 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

41from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

42from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

43from camcops_server.cc_modules.cc_text import SS 

44from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

45 

46 

47# ============================================================================= 

48# GDS-15 

49# ============================================================================= 

50 

51class Gds15Metaclass(DeclarativeMeta): 

52 # noinspection PyInitNewSignature 

53 def __init__(cls: Type['Gds15'], 

54 name: str, 

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

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

57 add_multiple_columns( 

58 cls, "q", 1, cls.NQUESTIONS, String(length=1), 

59 pv=[NO_CHAR, YES_CHAR], 

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

61 comment_strings=[ 

62 "satisfied", 

63 "dropped activities", 

64 "life empty", 

65 "bored", 

66 "good spirits", # 5 

67 "afraid", 

68 "happy", 

69 "helpless", 

70 "stay at home", 

71 "memory problems", # 10 

72 "wonderful to be alive", 

73 "worthless", 

74 "full of energy", 

75 "hopeless", 

76 "others better off", # 15 

77 ] 

78 ) 

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

80 

81 

82class Gds15(TaskHasPatientMixin, Task, 

83 metaclass=Gds15Metaclass): 

84 """ 

85 Server implementation of the GDS-15 task. 

86 """ 

87 __tablename__ = "gds15" 

88 shortname = "GDS-15" 

89 provides_trackers = True 

90 

91 NQUESTIONS = 15 

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

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

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

95 MAX_SCORE = 15 

96 

97 @staticmethod 

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

99 _ = req.gettext 

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

101 

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

103 return [TrackerInfo( 

104 value=self.total_score(), 

105 plot_label="GDS-15 total score", 

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

107 axis_min=-0.5, 

108 axis_max=self.MAX_SCORE + 0.5 

109 )] 

110 

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

112 if not self.is_complete(): 

113 return CTV_INCOMPLETE 

114 return [CtvInfo( 

115 content=f"GDS-15 total score {self.total_score()}/{self.MAX_SCORE}" 

116 )] 

117 

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

119 return self.standard_task_summary_fields() + [ 

120 SummaryElement(name="total", 

121 coltype=Integer(), 

122 value=self.total_score(), 

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

124 ] 

125 

126 def is_complete(self) -> bool: 

127 return ( 

128 self.all_fields_not_none(self.TASK_FIELDS) and 

129 self.field_contents_valid() 

130 ) 

131 

132 def total_score(self) -> int: 

133 score = 0 

134 for q in self.SCORE_IF_YES: 

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

136 score += 1 

137 for q in self.SCORE_IF_NO: 

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

139 score += 1 

140 return score 

141 

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

143 score = self.total_score() 

144 

145 q_a = "" 

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

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

148 q_a += tr_qa( 

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

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

151 ) 

152 

153 return f""" 

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

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

156 {self.get_is_complete_tr(req)} 

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

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

159 </table> 

160 </div> 

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

162 Ratings are over the last 1 week. 

163 </div> 

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

165 <tr> 

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

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

168 </tr> 

169 {q_a} 

170 </table> 

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

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

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

174 </div> 

175 """ 

176 

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

178 codes = [SnomedExpression(req.snomed(SnomedLookup.GDS15_PROCEDURE_ASSESSMENT))] # noqa 

179 if self.is_complete(): 

180 codes.append(SnomedExpression( 

181 req.snomed(SnomedLookup.GDS15_SCALE), 

182 { 

183 req.snomed(SnomedLookup.GDS15_SCORE): self.total_score(), 

184 } 

185 )) 

186 return codes