Coverage for tasks/gds.py: 57%

1"""

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

27

28 from typing import Any, List, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31 from sqlalchemy.sql.sqltypes import Integer, String

32

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

34 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

35 from camcops_server.cc_modules.cc_db import add_multiple_columns

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

37 from camcops_server.cc_modules.cc_request import CamcopsRequest

38 from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup

39 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

40 from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin

41 from camcops_server.cc_modules.cc_text import SS

42 from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo

43

44

45# =============================================================================

46# GDS-15

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

48

49

50 class Gds15( # type: ignore[misc]

51 TaskHasPatientMixin,

52 Task,

53):

54 """

55 Server implementation of the GDS-15 task.

56 """

57

58 __tablename__ = "gds15"

59 shortname = "GDS-15"

60 info_filename_stem = "gds"

61 provides_trackers = True

62

63 NQUESTIONS = 15

64

65 @classmethod

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

67 add_multiple_columns(

68 cls,

69 "q",

70 1,

71 cls.NQUESTIONS,

72 String(length=1),

73 pv=[NO_CHAR, YES_CHAR],

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

75 comment_strings=[

76 "satisfied",

77 "dropped activities",

78 "life empty",

79 "bored",

80 "good spirits", # 5

81 "afraid",

82 "happy",

83 "helpless",

84 "stay at home",

85 "memory problems", # 10

86 "wonderful to be alive",

87 "worthless",

88 "full of energy",

89 "hopeless",

90 "others better off", # 15

91 ],

92 )

93

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

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

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

97 MAX_SCORE = 15

98

99 @staticmethod

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

101 _ = req.gettext

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

103

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

105 return [

106 TrackerInfo(

107 value=self.total_score(),

108 plot_label="GDS-15 total score",

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

110 axis_min=-0.5,

111 axis_max=self.MAX_SCORE + 0.5,

112 )

113 ]

114

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

116 if not self.is_complete():

117 return CTV_INCOMPLETE

118 return [

119 CtvInfo(

120 content=f"GDS-15 total score "

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

122 )

123 ]

124

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

126 return self.standard_task_summary_fields() + [

127 SummaryElement(

128 name="total",

129 coltype=Integer(),

130 value=self.total_score(),

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

132 )

133 ]

134

135 def is_complete(self) -> bool:

136 return (

137 self.all_fields_not_none(self.TASK_FIELDS)

138 and self.field_contents_valid()

139 )

140

141 def total_score(self) -> int:

142 score = 0

143 for q in self.SCORE_IF_YES:

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

145 score += 1

146 for q in self.SCORE_IF_NO:

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

148 score += 1

149 return score

150

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

152 score = self.total_score()

153

154 q_a = ""

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

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

157 q_a += tr_qa(

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

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

160 )

161

162 return f"""

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

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

165 {self.get_is_complete_tr(req)}

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

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

168 </table>

169 </div>

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

171 Ratings are over the last 1 week.

172 </div>

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

174 <tr>

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

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

177 </tr>

178 {q_a}

179 </table>

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

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

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

183 </div>

184 """

185

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

187 codes = [

188 SnomedExpression(

189 req.snomed(SnomedLookup.GDS15_PROCEDURE_ASSESSMENT)

190 )

191 ]

192 if self.is_complete():

193 codes.append(

194 SnomedExpression(

195 req.snomed(SnomedLookup.GDS15_SCALE),

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

197 )

198 )

199 return codes