Coverage for tasks/aims.py: 61%

61 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/aims.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 

35 

36from camcops_server.cc_modules.cc_constants import CssClass, PV 

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 ( 

40 answer, 

41 get_yes_no_none, 

42 tr, 

43 tr_qa, 

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_summaryelement import SummaryElement 

48from camcops_server.cc_modules.cc_task import ( 

49 get_from_dict, 

50 Task, 

51 TaskHasClinicianMixin, 

52 TaskHasPatientMixin, 

53) 

54from camcops_server.cc_modules.cc_text import SS 

55from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

56 

57 

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

59# AIMS 

60# ============================================================================= 

61 

62 

63class AimsMetaclass(DeclarativeMeta): 

64 # noinspection PyInitNewSignature 

65 def __init__( 

66 cls: Type["Aims"], 

67 name: str, 

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

69 classdict: Dict[str, Any], 

70 ) -> None: 

71 add_multiple_columns( 

72 cls, 

73 "q", 

74 1, 

75 cls.NSCOREDQUESTIONS, 

76 minimum=0, 

77 maximum=4, 

78 comment_fmt="Q{n}, {s} (0 none - 4 severe)", 

79 comment_strings=[ 

80 "facial_expression", 

81 "lips", 

82 "jaw", 

83 "tongue", 

84 "upper_limbs", 

85 "lower_limbs", 

86 "trunk", 

87 "global", 

88 "incapacitation", 

89 "awareness", 

90 ], 

91 ) 

92 add_multiple_columns( 

93 cls, 

94 "q", 

95 cls.NSCOREDQUESTIONS + 1, 

96 cls.NQUESTIONS, 

97 pv=PV.BIT, 

98 comment_fmt="Q{n}, {s} (not scored) (0 no, 1 yes)", 

99 comment_strings=[ 

100 "problems_teeth_dentures", 

101 "usually_wears_dentures", 

102 ], 

103 ) 

104 

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

106 

107 

108class Aims( 

109 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=AimsMetaclass 

110): 

111 """ 

112 Server implementation of the AIMS task. 

113 """ 

114 

115 __tablename__ = "aims" 

116 shortname = "AIMS" 

117 provides_trackers = True 

118 

119 NQUESTIONS = 12 

120 NSCOREDQUESTIONS = 10 

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

122 SCORED_FIELDS = strseq("q", 1, NSCOREDQUESTIONS) 

123 

124 @staticmethod 

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

126 _ = req.gettext 

127 return _("Abnormal Involuntary Movement Scale") 

128 

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

130 return [ 

131 TrackerInfo( 

132 value=self.total_score(), 

133 plot_label="AIMS total score", 

134 axis_label="Total score (out of 40)", 

135 axis_min=-0.5, 

136 axis_max=40.5, 

137 ) 

138 ] 

139 

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

141 if not self.is_complete(): 

142 return CTV_INCOMPLETE 

143 return [CtvInfo(content=f"AIMS total score {self.total_score()}/40")] 

144 

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

146 return self.standard_task_summary_fields() + [ 

147 SummaryElement( 

148 name="total", 

149 coltype=Integer(), 

150 value=self.total_score(), 

151 comment="Total score (/40)", 

152 ) 

153 ] 

154 

155 def is_complete(self) -> bool: 

156 return ( 

157 self.all_fields_not_none(Aims.TASK_FIELDS) 

158 and self.field_contents_valid() 

159 ) 

160 

161 def total_score(self) -> int: 

162 return self.sum_fields(self.SCORED_FIELDS) 

163 

164 # noinspection PyUnresolvedReferences 

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

166 score = self.total_score() 

167 main_dict = {None: None} 

168 q10_dict = {None: None} 

169 for option in range(0, 5): 

170 main_dict[option] = ( 

171 str(option) 

172 + " — " 

173 + self.wxstring(req, "main_option" + str(option)) 

174 ) 

175 q10_dict[option] = ( 

176 str(option) 

177 + " — " 

178 + self.wxstring(req, "q10_option" + str(option)) 

179 ) 

180 

181 q_a = "" 

182 for q in range(1, 10): 

183 q_a += tr_qa( 

184 self.wxstring(req, "q" + str(q) + "_s"), 

185 get_from_dict(main_dict, getattr(self, "q" + str(q))), 

186 ) 

187 q_a += ( 

188 tr_qa( 

189 self.wxstring(req, "q10_s"), get_from_dict(q10_dict, self.q10) 

190 ) 

191 + tr_qa( 

192 self.wxstring(req, "q11_s"), get_yes_no_none(req, self.q11) 

193 ) 

194 + tr_qa( 

195 self.wxstring(req, "q12_s"), get_yes_no_none(req, self.q12) 

196 ) 

197 ) 

198 

199 return f""" 

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

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

202 {self.get_is_complete_tr(req)} 

203 {tr(req.sstring(SS.TOTAL_SCORE) + " <sup>[1]</sup>", 

204 answer(score) + " / 40")} 

205 </table> 

206 </div> 

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

208 <tr> 

209 <th width="50%">Question</th> 

210 <th width="50%">Answer</th> 

211 </tr> 

212 {q_a} 

213 </table> 

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

215 [1] Only Q1–10 are scored. 

216 </div> 

217 """ 

218 

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

220 codes = [ 

221 SnomedExpression( 

222 req.snomed(SnomedLookup.AIMS_PROCEDURE_ASSESSMENT) 

223 ) 

224 ] 

225 if self.is_complete(): 

226 codes.append( 

227 SnomedExpression( 

228 req.snomed(SnomedLookup.AIMS_SCALE), 

229 { 

230 req.snomed( 

231 SnomedLookup.AIMS_TOTAL_SCORE 

232 ): self.total_score() # noqa 

233 }, 

234 ) 

235 ) 

236 return codes