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/cage.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 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_db import add_multiple_columns 

37from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

38from camcops_server.cc_modules.cc_html import ( 

39 answer, 

40 get_yes_no, 

41 tr, 

42 tr_qa, 

43) 

44from camcops_server.cc_modules.cc_request import CamcopsRequest 

45from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

46from camcops_server.cc_modules.cc_sqla_coltypes import CharColType 

47from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

48from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

49from camcops_server.cc_modules.cc_text import SS 

50from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

51 

52 

53# ============================================================================= 

54# CAGE 

55# ============================================================================= 

56 

57class CageMetaclass(DeclarativeMeta): 

58 # noinspection PyInitNewSignature 

59 def __init__(cls: Type['Cage'], 

60 name: str, 

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

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

63 add_multiple_columns( 

64 cls, "q", 1, cls.NQUESTIONS, CharColType, 

65 pv=['Y', 'N'], 

66 comment_fmt="Q{n}, {s} (Y, N)", 

67 comment_strings=["C", "A", "G", "E"] 

68 ) 

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

70 

71 

72class Cage(TaskHasPatientMixin, Task, 

73 metaclass=CageMetaclass): 

74 """ 

75 Server implementation of the CAGE task. 

76 """ 

77 __tablename__ = "cage" 

78 shortname = "CAGE" 

79 provides_trackers = True 

80 

81 NQUESTIONS = 4 

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

83 

84 @staticmethod 

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

86 _ = req.gettext 

87 return _("CAGE Questionnaire") 

88 

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

90 return [TrackerInfo( 

91 value=self.total_score(), 

92 plot_label="CAGE total score", 

93 axis_label=f"Total score (out of {self.NQUESTIONS})", 

94 axis_min=-0.5, 

95 axis_max=self.NQUESTIONS + 0.5, 

96 horizontal_lines=[1.5] 

97 )] 

98 

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

100 if not self.is_complete(): 

101 return CTV_INCOMPLETE 

102 return [CtvInfo( 

103 content=f"CAGE score {self.total_score()}/{self.NQUESTIONS}" 

104 )] 

105 

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

107 return self.standard_task_summary_fields() + [ 

108 SummaryElement( 

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

110 value=self.total_score(), 

111 comment=f"Total score (/{self.NQUESTIONS})"), 

112 ] 

113 

114 def is_complete(self) -> bool: 

115 return ( 

116 self.all_fields_not_none(Cage.TASK_FIELDS) and 

117 self.field_contents_valid() 

118 ) 

119 

120 def get_value(self, q: int) -> int: 

121 return 1 if getattr(self, "q" + str(q)) == "Y" else 0 

122 

123 def total_score(self) -> int: 

124 total = 0 

125 for i in range(1, Cage.NQUESTIONS + 1): 

126 total += self.get_value(i) 

127 return total 

128 

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

130 score = self.total_score() 

131 exceeds_cutoff = score >= 2 

132 q_a = "" 

133 for q in range(1, Cage.NQUESTIONS + 1): 

134 q_a += tr_qa(str(q) + " — " + self.wxstring(req, "q" + str(q)), 

135 getattr(self, "q" + str(q))) # answer is itself Y/N/NULL # noqa 

136 total_score = tr( 

137 req.sstring(SS.TOTAL_SCORE), 

138 answer(score) + f" / {self.NQUESTIONS}" 

139 ) 

140 over_threshold = tr_qa( 

141 self.wxstring(req, "over_threshold"), 

142 get_yes_no(req, exceeds_cutoff) 

143 ) 

144 return f""" 

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

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

147 {self.get_is_complete_tr(req)} 

148 {total_score} 

149 {over_threshold} 

150 </table> 

151 </div> 

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

153 <tr> 

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

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

156 </tr> 

157 {q_a} 

158 </table> 

159 """ 

160 

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

162 codes = [SnomedExpression(req.snomed(SnomedLookup.CAGE_PROCEDURE_ASSESSMENT))] # noqa 

163 if self.is_complete(): 

164 codes.append(SnomedExpression( 

165 req.snomed(SnomedLookup.CAGE_SCALE), 

166 { 

167 req.snomed(SnomedLookup.CAGE_SCORE): self.total_score(), 

168 } 

169 )) 

170 return codes