Coverage for tasks/apeqpt.py: 48%

61 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/apeqpt.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- By Joe Kearney, Rudolf Cardinal. 

27 

28""" 

29 

30from typing import Dict, List, Optional 

31 

32from pendulum import DateTime as Pendulum 

33from sqlalchemy.orm import Mapped 

34from sqlalchemy.sql.sqltypes import UnicodeText 

35 

36from camcops_server.cc_modules.cc_constants import CssClass 

37from camcops_server.cc_modules.cc_fhir import ( 

38 FHIRAnsweredQuestion, 

39 FHIRAnswerType, 

40 FHIRQuestionType, 

41) 

42from camcops_server.cc_modules.cc_html import tr_qa 

43from camcops_server.cc_modules.cc_request import CamcopsRequest 

44from camcops_server.cc_modules.cc_sqla_coltypes import ( 

45 mapped_camcops_column, 

46 PendulumDateTimeAsIsoTextColType, 

47 ZERO_TO_ONE_CHECKER, 

48 ZERO_TO_TWO_CHECKER, 

49 ZERO_TO_FOUR_CHECKER, 

50) 

51from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

52from camcops_server.cc_modules.cc_task import get_from_dict, Task 

53 

54 

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

56# APEQPT 

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

58 

59 

60class Apeqpt(Task): 

61 """ 

62 Server implementation of the APEQPT task. 

63 """ 

64 

65 __tablename__ = "apeqpt" 

66 shortname = "APEQPT" 

67 provides_trackers = True 

68 

69 # todo: remove q_datetime (here and in the C++) -- it duplicates when_created # noqa 

70 q_datetime: Mapped[Optional[Pendulum]] = mapped_camcops_column( 

71 PendulumDateTimeAsIsoTextColType, 

72 comment="Date/time the assessment tool was completed", 

73 ) 

74 

75 N_CHOICE_QUESTIONS = 3 

76 q1_choice: Mapped[Optional[int]] = mapped_camcops_column( 

77 comment="Enough information was provided (0 no, 1 yes)", 

78 permitted_value_checker=ZERO_TO_ONE_CHECKER, 

79 ) 

80 q2_choice: Mapped[Optional[int]] = mapped_camcops_column( 

81 comment="Treatment preference (0 no, 1 yes)", 

82 permitted_value_checker=ZERO_TO_ONE_CHECKER, 

83 ) 

84 q3_choice: Mapped[Optional[int]] = mapped_camcops_column( 

85 comment="Preference offered (0 no, 1 yes, 2 N/A)", 

86 permitted_value_checker=ZERO_TO_TWO_CHECKER, 

87 ) 

88 

89 q1_satisfaction: Mapped[Optional[int]] = mapped_camcops_column( 

90 comment=( 

91 "Patient satisfaction (0 not at all satisfied - " 

92 "4 completely satisfied)" 

93 ), 

94 permitted_value_checker=ZERO_TO_FOUR_CHECKER, 

95 ) 

96 q2_satisfaction: Mapped[Optional[str]] = mapped_camcops_column( 

97 UnicodeText, comment="Service experience" 

98 ) 

99 

100 MAIN_QUESTIONS = [ 

101 "q_datetime", 

102 "q1_choice", 

103 "q2_choice", 

104 "q3_choice", 

105 "q1_satisfaction", 

106 ] 

107 

108 @staticmethod 

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

110 _ = req.gettext 

111 return _( 

112 "Assessment Patient Experience Questionnaire " 

113 "for Psychological Therapies" 

114 ) 

115 

116 def is_complete(self) -> bool: 

117 if self.any_fields_none(self.MAIN_QUESTIONS): 

118 return False 

119 if not self.field_contents_valid(): 

120 return False 

121 return True 

122 

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

124 return self.standard_task_summary_fields() 

125 

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

127 c_dict = { 

128 0: "0 — " + self.wxstring(req, "a0_choice"), 

129 1: "1 — " + self.wxstring(req, "a1_choice"), 

130 2: "2 — " + self.wxstring(req, "a2_choice"), 

131 } 

132 s_dict = { 

133 0: "0 — " + self.wxstring(req, "a0_satisfaction"), 

134 1: "1 — " + self.wxstring(req, "a1_satisfaction"), 

135 2: "2 — " + self.wxstring(req, "a2_satisfaction"), 

136 3: "3 — " + self.wxstring(req, "a3_satisfaction"), 

137 4: "4 — " + self.wxstring(req, "a4_satisfaction"), 

138 } 

139 q_a = "" 

140 for i in range(1, self.N_CHOICE_QUESTIONS + 1): 

141 nstr = str(i) 

142 q_a += tr_qa( 

143 self.wxstring(req, "q" + nstr + "_choice"), 

144 get_from_dict(c_dict, getattr(self, "q" + nstr + "_choice")), 

145 ) 

146 

147 q_a += tr_qa( 

148 self.wxstring(req, "q1_satisfaction"), 

149 get_from_dict(s_dict, self.q1_satisfaction), 

150 ) 

151 q_a += tr_qa( 

152 self.wxstring(req, "q2_satisfaction"), 

153 self.q2_satisfaction, 

154 default="", 

155 ) 

156 

157 return f""" 

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

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

160 {self.get_is_complete_tr(req)} 

161 </table> 

162 </div> 

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

164 Patient satisfaction rating for service provided. The service 

165 is rated on choice offered and general satisfaction. 

166 </div> 

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

168 <tr> 

169 <th width="60%">Question</th> 

170 <th width="40%">Answer</th> 

171 </tr> 

172 {q_a} 

173 </table> 

174 """ 

175 

176 def get_fhir_questionnaire( 

177 self, req: "CamcopsRequest" 

178 ) -> List[FHIRAnsweredQuestion]: 

179 items = [] # type: List[FHIRAnsweredQuestion] 

180 

181 yes_no_options = {} # type: Dict[int, str] 

182 for index in range(2): 

183 yes_no_options[index] = self.wxstring(req, f"a{index}_choice") 

184 items.append( 

185 FHIRAnsweredQuestion( 

186 qname="q1_choice", 

187 qtext=self.wxstring(req, "q1_choice"), 

188 qtype=FHIRQuestionType.CHOICE, 

189 answer_type=FHIRAnswerType.INTEGER, 

190 answer=self.q1_choice, 

191 answer_options=yes_no_options, 

192 ) 

193 ) 

194 items.append( 

195 FHIRAnsweredQuestion( 

196 qname="q2_choice", 

197 qtext=self.wxstring(req, "q2_choice"), 

198 qtype=FHIRQuestionType.CHOICE, 

199 answer_type=FHIRAnswerType.INTEGER, 

200 answer=self.q2_choice, 

201 answer_options=yes_no_options, 

202 ) 

203 ) 

204 

205 yes_no_na_options = yes_no_options.copy() 

206 yes_no_na_options[2] = self.wxstring(req, "a2_choice") 

207 items.append( 

208 FHIRAnsweredQuestion( 

209 qname="q3_choice", 

210 qtext=self.wxstring(req, "q3_choice"), 

211 qtype=FHIRQuestionType.CHOICE, 

212 answer_type=FHIRAnswerType.INTEGER, 

213 answer=self.q3_choice, 

214 answer_options=yes_no_na_options, 

215 ) 

216 ) 

217 

218 satisfaction_options = {} # type: Dict[int, str] 

219 for index in range(5): 

220 satisfaction_options[index] = self.wxstring( 

221 req, f"a{index}_satisfaction" 

222 ) 

223 items.append( 

224 FHIRAnsweredQuestion( 

225 qname="q1_satisfaction", 

226 qtext=self.xstring(req, "q1_satisfaction"), 

227 qtype=FHIRQuestionType.CHOICE, 

228 answer_type=FHIRAnswerType.INTEGER, 

229 answer=self.q1_satisfaction, 

230 answer_options=satisfaction_options, 

231 ) 

232 ) 

233 

234 items.append( 

235 FHIRAnsweredQuestion( 

236 qname="q2_satisfaction", 

237 qtext=self.xstring(req, "q2_satisfaction"), 

238 qtype=FHIRQuestionType.STRING, 

239 answer_type=FHIRAnswerType.STRING, 

240 answer=self.q2_satisfaction, 

241 ) 

242 ) 

243 

244 return items