Coverage for tasks/service_satisfaction.py: 69%

58 statements  

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

1""" 

2camcops_server/tasks/service_satisfaction.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""" 

27 

28from typing import Optional 

29 

30from sqlalchemy.orm import Mapped, mapped_column 

31from sqlalchemy.sql.sqltypes import UnicodeText 

32 

33from camcops_server.cc_modules.cc_constants import CssClass 

34from camcops_server.cc_modules.cc_html import tr_qa 

35from camcops_server.cc_modules.cc_request import CamcopsRequest 

36from camcops_server.cc_modules.cc_sqla_coltypes import ( 

37 mapped_camcops_column, 

38 ZERO_TO_FOUR_CHECKER, 

39) 

40from camcops_server.cc_modules.cc_string import AS 

41from camcops_server.cc_modules.cc_task import ( 

42 get_from_dict, 

43 Task, 

44 TaskHasPatientMixin, 

45) 

46 

47 

48# ============================================================================= 

49# Abstract base class 

50# ============================================================================= 

51 

52 

53class AbstractSatisfaction(object): 

54 # noinspection PyMethodParameters 

55 service: Mapped[Optional[str]] = mapped_column( 

56 "service", UnicodeText, comment="Clinical service being rated" 

57 ) 

58 

59 # noinspection PyMethodParameters 

60 rating: Mapped[Optional[int]] = mapped_camcops_column( 

61 "rating", 

62 permitted_value_checker=ZERO_TO_FOUR_CHECKER, 

63 comment="Rating (0 very poor - 4 excellent)", 

64 ) 

65 

66 # noinspection PyMethodParameters 

67 good: Mapped[Optional[str]] = mapped_column( 

68 "good", UnicodeText, comment="What has been good?" 

69 ) 

70 

71 # noinspection PyMethodParameters 

72 bad: Mapped[Optional[str]] = mapped_column( 

73 "bad", UnicodeText, comment="What could be improved?" 

74 ) 

75 

76 TASK_FIELDS = ["service", "rating", "good", "bad"] 

77 

78 def is_complete(self) -> bool: 

79 # noinspection PyUnresolvedReferences 

80 return self.rating is not None and self.field_contents_valid() # type: ignore[attr-defined] # noqa: E501 

81 # ... self.field_contents_valid() is from Task, and we are a mixin 

82 

83 def get_rating_text(self, req: CamcopsRequest) -> Optional[str]: 

84 ratingdict = { 

85 None: None, 

86 0: req.wappstring(AS.SATIS_RATING_A_PREFIX + "0"), 

87 1: req.wappstring(AS.SATIS_RATING_A_PREFIX + "1"), 

88 2: req.wappstring(AS.SATIS_RATING_A_PREFIX + "2"), 

89 3: req.wappstring(AS.SATIS_RATING_A_PREFIX + "3"), 

90 4: req.wappstring(AS.SATIS_RATING_A_PREFIX + "4"), 

91 } 

92 return get_from_dict(ratingdict, self.rating) 

93 

94 def get_common_task_html( 

95 self, req: CamcopsRequest, rating_q: str, good_q: str, bad_q: str 

96 ) -> str: 

97 if self.rating is not None: 

98 r = f"{self.rating}. {self.get_rating_text(req)}" 

99 else: 

100 r = None 

101 

102 is_complete_row = self.get_is_complete_tr(req) # type: ignore[attr-defined] # noqa: E501 

103 

104 # noinspection PyUnresolvedReferences 

105 return f""" 

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

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

108 {is_complete_row} 

109 </table> 

110 </div> 

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

112 <tr> 

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

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

115 </tr> 

116 {tr_qa(req.wappstring(AS.SATIS_SERVICE_BEING_RATED), 

117 self.service)} 

118 {tr_qa(f"{rating_q} {self.service}?", r)} 

119 {tr_qa(good_q, self.good)} 

120 {tr_qa(bad_q, self.bad)} 

121 </table> 

122 """ 

123 # ... self.get_is_complete_tr() is from Task, and we are a mixin 

124 

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

126 raise NotImplementedError("implement in subclass") 

127 

128 

129# ============================================================================= 

130# PatientSatisfaction 

131# ============================================================================= 

132 

133 

134class PatientSatisfaction(TaskHasPatientMixin, AbstractSatisfaction, Task): # type: ignore[misc] # noqa: E501 

135 """ 

136 Server implementation of the PatientSatisfaction task. 

137 """ 

138 

139 __tablename__ = "pt_satis" 

140 shortname = "PatientSatisfaction" 

141 info_filename_stem = "pss" 

142 

143 @staticmethod 

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

145 _ = req.gettext 

146 return _("Patient Satisfaction Scale") 

147 

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

149 return self.get_common_task_html( 

150 req, 

151 req.wappstring(AS.SATIS_PT_RATING_Q), 

152 req.wappstring(AS.SATIS_GOOD_Q), 

153 req.wappstring(AS.SATIS_BAD_Q), 

154 ) 

155 

156 

157# ============================================================================= 

158# ReferrerSatisfactionGen 

159# ============================================================================= 

160 

161 

162class ReferrerSatisfactionGen(AbstractSatisfaction, Task): 

163 """ 

164 Server implementation of the ReferrerSatisfactionSurvey task. 

165 """ 

166 

167 __tablename__ = "ref_satis_gen" 

168 shortname = "ReferrerSatisfactionSurvey" 

169 info_filename_stem = "rss" 

170 

171 @staticmethod 

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

173 _ = req.gettext 

174 return _("Referrer Satisfaction Scale, survey") 

175 

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

177 return self.get_common_task_html( 

178 req, 

179 req.wappstring(AS.SATIS_REF_GEN_RATING_Q), 

180 req.wappstring(AS.SATIS_GOOD_Q), 

181 req.wappstring(AS.SATIS_BAD_Q), 

182 ) 

183 

184 

185# ============================================================================= 

186# ReferrerSatisfactionSpec 

187# ============================================================================= 

188 

189 

190class ReferrerSatisfactionSpec( # type: ignore[misc] 

191 TaskHasPatientMixin, AbstractSatisfaction, Task 

192): 

193 """ 

194 Server implementation of the ReferrerSatisfactionSpecific task. 

195 """ 

196 

197 __tablename__ = "ref_satis_spec" 

198 shortname = "ReferrerSatisfactionSpecific" 

199 info_filename_stem = "rss" 

200 

201 @staticmethod 

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

203 _ = req.gettext 

204 return _("Referrer Satisfaction Scale, patient-specific") 

205 

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

207 return self.get_common_task_html( 

208 req, 

209 req.wappstring(AS.SATIS_REF_SPEC_RATING_Q), 

210 req.wappstring(AS.SATIS_GOOD_Q), 

211 req.wappstring(AS.SATIS_BAD_Q), 

212 )