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/badls.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 ( 

36 CssClass, 

37 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

38) 

39from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

40from camcops_server.cc_modules.cc_db import add_multiple_columns 

41from camcops_server.cc_modules.cc_html import ( 

42 answer, 

43 tr, 

44) 

45from camcops_server.cc_modules.cc_request import CamcopsRequest 

46from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup 

47from camcops_server.cc_modules.cc_sqla_coltypes import CharColType 

48from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

49from camcops_server.cc_modules.cc_task import ( 

50 Task, 

51 TaskHasPatientMixin, 

52 TaskHasRespondentMixin, 

53) 

54 

55 

56# ============================================================================= 

57# BADLS 

58# ============================================================================= 

59 

60class BadlsMetaclass(DeclarativeMeta): 

61 # noinspection PyInitNewSignature 

62 def __init__(cls: Type['Badls'], 

63 name: str, 

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

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

66 add_multiple_columns( 

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

68 comment_fmt="Q{n}, {s} ('a' best [0] to 'd' worst [3]; " 

69 "'e'=N/A [scored 0])", 

70 pv=list(cls.SCORING.keys()), 

71 comment_strings=cls.QUESTION_SNIPPETS 

72 ) 

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

74 

75 

76class Badls(TaskHasPatientMixin, TaskHasRespondentMixin, Task, 

77 metaclass=BadlsMetaclass): 

78 """ 

79 Server implementation of the BADLS task. 

80 """ 

81 __tablename__ = "badls" 

82 shortname = "BADLS" 

83 provides_trackers = True 

84 

85 SCORING = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 0} 

86 NQUESTIONS = 20 

87 QUESTION_SNIPPETS = [ 

88 "food", # 1 

89 "eating", 

90 "drink", 

91 "drinking", 

92 "dressing", # 5 

93 "hygiene", 

94 "teeth", 

95 "bath/shower", 

96 "toilet/commode", 

97 "transfers", # 10 

98 "mobility", 

99 "orientation: time", 

100 "orientation: space", 

101 "communication", 

102 "telephone", # 15 

103 "hosuework/gardening", 

104 "shopping", 

105 "finances", 

106 "games/hobbies", 

107 "transport", # 20 

108 ] 

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

110 

111 @staticmethod 

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

113 _ = req.gettext 

114 return _("Bristol Activities of Daily Living Scale") 

115 

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

117 return self.standard_task_summary_fields() + [ 

118 SummaryElement(name="total_score", 

119 coltype=Integer(), 

120 value=self.total_score(), 

121 comment="Total score (/ 48)"), 

122 ] 

123 

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

125 if not self.is_complete(): 

126 return CTV_INCOMPLETE 

127 return [CtvInfo( 

128 content="BADLS total score {}/60 (lower is better)".format( 

129 self.total_score()) 

130 )] 

131 

132 def score(self, q: str) -> int: 

133 text_value = getattr(self, q) 

134 return self.SCORING.get(text_value, 0) 

135 

136 def total_score(self) -> int: 

137 return sum(self.score(q) for q in self.TASK_FIELDS) 

138 

139 def is_complete(self) -> bool: 

140 return ( 

141 self.field_contents_valid() and 

142 self.is_respondent_complete() and 

143 self.all_fields_not_none(self.TASK_FIELDS) 

144 ) 

145 

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

147 q_a = "" 

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

149 fieldname = "q" + str(q) 

150 qtext = self.wxstring(req, fieldname) # happens to be the same 

151 avalue = getattr(self, "q" + str(q)) 

152 atext = (self.wxstring(req, "q{}_{}".format(q, avalue)) 

153 if q is not None else None) 

154 score = self.score(fieldname) 

155 q_a += tr(qtext, answer(atext), score) 

156 return f""" 

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

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

159 {self.get_is_complete_tr(req)} 

160 <tr> 

161 <td>Total score (0–60, higher worse)</td> 

162 <td>{answer(self.total_score())}</td> 

163 </td> 

164 </table> 

165 </div> 

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

167 <tr> 

168 <th width="30%">Question</th> 

169 <th width="50%">Answer <sup>[1]</sup></th> 

170 <th width="20%">Score</th> 

171 </tr> 

172 {q_a} 

173 </table> 

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

175 [1] Scored a = 0, b = 1, c = 2, d = 3, e = 0. 

176 </div> 

177 {DATA_COLLECTION_UNLESS_UPGRADED_DIV} 

178 """ 

179 

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

181 # The BADLS is ALWAYS carer-rated, so it's appropriate to put the 

182 # SNOMED-CT codes in. 

183 codes = [SnomedExpression(req.snomed(SnomedLookup.BADLS_PROCEDURE_ASSESSMENT))] # noqa 

184 if self.is_complete(): 

185 codes.append(SnomedExpression( 

186 req.snomed(SnomedLookup.BADLS_SCALE), 

187 { 

188 req.snomed(SnomedLookup.BADLS_SCORE): self.total_score(), 

189 } 

190 )) 

191 return codes