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/aims.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, PV 

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 ( 

39 answer, 

40 get_yes_no_none, 

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

47from camcops_server.cc_modules.cc_task import ( 

48 get_from_dict, 

49 Task, 

50 TaskHasClinicianMixin, 

51 TaskHasPatientMixin, 

52) 

53from camcops_server.cc_modules.cc_text import SS 

54from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

55 

56 

57# ============================================================================= 

58# AIMS 

59# ============================================================================= 

60 

61class AimsMetaclass(DeclarativeMeta): 

62 # noinspection PyInitNewSignature 

63 def __init__(cls: Type['Aims'], 

64 name: str, 

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

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

67 add_multiple_columns( 

68 cls, "q", 1, cls.NSCOREDQUESTIONS, 

69 minimum=0, maximum=4, 

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

71 comment_strings=["facial_expression", "lips", "jaw", "tongue", 

72 "upper_limbs", "lower_limbs", "trunk", "global", 

73 "incapacitation", "awareness"] 

74 ) 

75 add_multiple_columns( 

76 cls, "q", cls.NSCOREDQUESTIONS + 1, cls.NQUESTIONS, pv=PV.BIT, 

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

78 comment_strings=["problems_teeth_dentures", 

79 "usually_wears_dentures"] 

80 ) 

81 

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

83 

84 

85class Aims(TaskHasPatientMixin, TaskHasClinicianMixin, Task, 

86 metaclass=AimsMetaclass): 

87 """ 

88 Server implementation of the AIMS task. 

89 """ 

90 __tablename__ = "aims" 

91 shortname = "AIMS" 

92 provides_trackers = True 

93 

94 NQUESTIONS = 12 

95 NSCOREDQUESTIONS = 10 

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

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

98 

99 @staticmethod 

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

101 _ = req.gettext 

102 return _("Abnormal Involuntary Movement Scale") 

103 

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

105 return [TrackerInfo( 

106 value=self.total_score(), 

107 plot_label="AIMS total score", 

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

109 axis_min=-0.5, 

110 axis_max=40.5 

111 )] 

112 

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

114 if not self.is_complete(): 

115 return CTV_INCOMPLETE 

116 return [CtvInfo( 

117 content=f"AIMS total score {self.total_score()}/40" 

118 )] 

119 

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

121 return self.standard_task_summary_fields() + [ 

122 SummaryElement(name="total", 

123 coltype=Integer(), 

124 value=self.total_score(), 

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

126 ] 

127 

128 def is_complete(self) -> bool: 

129 return (self.all_fields_not_none(Aims.TASK_FIELDS) and 

130 self.field_contents_valid()) 

131 

132 def total_score(self) -> int: 

133 return self.sum_fields(self.SCORED_FIELDS) 

134 

135 # noinspection PyUnresolvedReferences 

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

137 score = self.total_score() 

138 main_dict = {None: None} 

139 q10_dict = {None: None} 

140 for option in range(0, 5): 

141 main_dict[option] = str(option) + " — " + \ 

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

143 q10_dict[option] = str(option) + " — " + \ 

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

145 

146 q_a = "" 

147 for q in range(1, 10): 

148 q_a += tr_qa( 

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

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

151 q_a += ( 

152 tr_qa(self.wxstring(req, "q10_s"), 

153 get_from_dict(q10_dict, self.q10)) + 

154 tr_qa(self.wxstring(req, "q11_s"), 

155 get_yes_no_none(req, self.q11)) + 

156 tr_qa(self.wxstring(req, "q12_s"), 

157 get_yes_no_none(req, self.q12)) 

158 ) 

159 

160 return f""" 

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

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

163 {self.get_is_complete_tr(req)} 

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

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

166 </table> 

167 </div> 

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

169 <tr> 

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

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

172 </tr> 

173 {q_a} 

174 </table> 

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

176 [1] Only Q1–10 are scored. 

177 </div> 

178 """ 

179 

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

181 codes = [SnomedExpression(req.snomed(SnomedLookup.AIMS_PROCEDURE_ASSESSMENT))] # noqa 

182 if self.is_complete(): 

183 codes.append(SnomedExpression( 

184 req.snomed(SnomedLookup.AIMS_SCALE), 

185 { 

186 req.snomed(SnomedLookup.AIMS_TOTAL_SCORE): self.total_score(), # noqa 

187 } 

188 )) 

189 return codes